Search code examples
clinuxserial-port

Serial communication in C


I am trying to write a simple application to read out a current value from a Keithley 6485 picoammeter, connected via serial communication (RS232<->USB) on linux.

Currently, such a value can be retrieved by doing all the needed initialization of the device and sending "READ?" to it: echo "READ?" > /dev/ttyUSB0. Then if cat /dev/ttyUSB0 has been listening, I get the following output: -2.250416E-14A,+8.320175E+03,+0.000000E+00,of which the first number is the desired value.

To be able to output the value I use the following code using termios libraries:

    /*====================================================================================================*/
    /* Serial Port Programming in C (Serial Port Read)                                                    */
/* Non Cannonical mode                                                                                */
/*----------------------------------------------------------------------------------------------------*/
    /* Program reads a string from the serial port at 9600 bps 8N1 format                                 */
/* Baudrate - 9600                                                                                    */
/* Stop bits -1                                                                                       */
/* No Parity                                                                                          */
    /*----------------------------------------------------------------------------------------------------*/
/* Compiler/IDE  : gcc 4.6.3                                                                          */
/* Library       :                                                                                    */
/* Commands      : gcc -o serialport_read serialport_read.c                                           */
/* OS            : Linux(x86) (Linux Mint 13 Maya)(Linux Kernel 3.x.x)                                */                              
/* Programmer    : Rahul.S                                                                            */
/* Date          : 21-December-2014                                                                   */
/*====================================================================================================*/

/*====================================================================================================*/
/* www.xanthium.in                                            */
/* Copyright (C) 2014 Rahul.S                                                                         */
/*====================================================================================================*/

/*====================================================================================================*/
/* Running the executable                                                                             */
/* ---------------------------------------------------------------------------------------------------*/ 
/* 1) Compile the  serialport_read.c  file using gcc on the terminal (without quotes)                 */
    /*                                                                                                    */
/*  " gcc -o serialport_read serialport_read.c "                                                  */
/*                                                                                                    */
    /* 2) Linux will not allow you to access the serial port from user space,you have to be root.So use   */
    /*    "sudo" command to execute the compiled binary as super user.                                    */
    /*                                                                                                    */
    /*       "sudo ./serialport_read"                                                                     */
/*                                                                                                    */
/*====================================================================================================*/

/*====================================================================================================*/
/* Sellecting the Serial port Number on Linux                                                         */
/* ---------------------------------------------------------------------------------------------------*/ 
/* /dev/ttyUSBx - when using USB to Serial Converter, where x can be 0,1,2...etc                      */
/* /dev/ttySx   - for PC hardware based Serial ports, where x can be 0,1,2...etc                      */
    /*====================================================================================================*/

/*-------------------------------------------------------------*/
    /* termios structure -  /usr/include/asm-generic/termbits.h    */ 
/* use "man termios" to get more info about  termios structure */
/*-------------------------------------------------------------*/

    #include <stdio.h>
    #include <fcntl.h>   /* File Control Definitions           */
    #include <termios.h> /* POSIX Terminal Control Definitions */
    #include <unistd.h>  /* UNIX Standard Definitions      */ 
    #include <errno.h>   /* ERROR Number Definitions           */

void main(void)
    {
        int fd;/*File Descriptor*/

    printf("\n +----------------------------------+");
    printf("\n |        Serial Port Read          |");
    printf("\n +----------------------------------+");

    /*------------------------------- Opening the Serial Port -------------------------------*/

    /* Change /dev/ttyUSB0 to the one corresponding to your system */

        fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);    /* ttyUSB0 is the FT232 based USB2SERIAL Converter   */
    //  fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NDELAY); /* ttyUSB0 is the FT232 based USB2SERIAL Converter   */
                            /* O_RDWR   - Read/Write access to serial port       */
                            /* O_NOCTTY - No terminal will control the process   */
                            /* Open in blocking mode,read will wait              */



        if(fd == -1)                        /* Error Checking */
               printf("\n  Error! in Opening ttyUSB0  ");
        else
               printf("\n  ttyUSB0 Opened Successfully ");


    /*---------- Setting the Attributes of the serial port using termios structure --------- */

    struct termios SerialPortSettings;  /* Create the structure                          */

    tcgetattr(fd, &SerialPortSettings); /* Get the current attributes of the Serial port */

    /* Setting the Baud rate */
    cfsetispeed(&SerialPortSettings,B19200); /* Set Read  Speed as 19200                       */
    cfsetospeed(&SerialPortSettings,B19200); /* Set Write Speed as 19200                       */

    /* 8N1 Mode */
    SerialPortSettings.c_cflag &= ~PARENB;   /* Disables the Parity Enable bit(PARENB),So No Parity   */
    SerialPortSettings.c_cflag &= ~CSTOPB;   /* CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit */
    SerialPortSettings.c_cflag &= ~CSIZE;    /* Clears the mask for setting the data size             */
    SerialPortSettings.c_cflag |=  CS8;      /* Set the data bits = 8                                 */

    SerialPortSettings.c_cflag &= ~CRTSCTS;       /* No Hardware flow Control                         */
    SerialPortSettings.c_cflag |= CREAD | CLOCAL; /* Enable receiver,Ignore Modem Control lines       */ 


    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);          /* Disable XON/XOFF flow control both i/p and o/p */
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /* Non Cannonical mode                            */

    SerialPortSettings.c_oflag &= ~OPOST;/*No Output Processing*/

    /* Setting Time outs */
    SerialPortSettings.c_cc[VMIN] = 13; /* Read at least 10 characters */
    SerialPortSettings.c_cc[VTIME] = 0; /* Wait indefinetly   */


    if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) /* Set the attributes to the termios structure*/
        printf("\n  ERROR ! in Setting attributes");
    else
                printf("\n  BaudRate = 19200 \n  StopBits = 1 \n  Parity   = none");

        /*------------------------------- Read data from serial port -----------------------------*/

    char read_buffer[32];   /* Buffer to store the data received              */
    int  bytes_read = 0;    /* Number of bytes read by the read() system call */
    int i = 0;

    tcflush(fd, TCIFLUSH);   /* Discards old data in the rx buffer            */

    bytes_read = read(fd,&read_buffer,32); /* Read the data                   */

    printf("\n\n  Bytes Rxed -%d", bytes_read); /* Print the number of bytes read */
    printf("\n\n  ");
    for(i=0;i<13;i++)    /*printing only the needed bytes*/
        printf("%c",read_buffer[i]);

    printf("\n +----------------------------------+\n\n\n");

    close(fd); /* Close the serial port */

    }

Which outputs:

 +----------------------------------+
 |        Serial Port Read          |
 +----------------------------------+
  ttyUSB0 Opened Successfully 
  BaudRate = 19200 
  StopBits = 1 
  Parity   = none

  Bytes Rxed -13

  -2.864104E-14
 +----------------------------------+

However, for it to be able to read the value, I have to echo the "READ?" command (or write to the device by using a write() function) every time when I want to know the value.

How can I put both writing and reading to the same application in the most elegant way (e.g. without making of named pipes), as currently the read() function waits for anything to come from the device and during that time I can not send a "READ?" command from the same C code?


EDIT: Apparently my writing procedure does not seem to work properly:

Port settings:

    struct termios SerialPortSettings;  /* Create the structure                          */

    tcgetattr(fd, &SerialPortSettings); /* Get the current attributes of the Serial port */

    cfsetispeed(&SerialPortSettings,(speed_t)B19200); /* Set Read  Speed as 19200                       */
    cfsetospeed(&SerialPortSettings,(speed_t)B19200); /* Set Write Speed as 19200                       */

    SerialPortSettings.c_cflag &= ~PARENB;   /* Disables the Parity Enable bit(PARENB),So No Parity   */
    SerialPortSettings.c_cflag &= ~CSTOPB;   /* CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit */
    SerialPortSettings.c_cflag &= ~CSIZE;    /* Clears the mask for setting the data size             */
    SerialPortSettings.c_cflag |=  CS8;      /* Set the data bits = 8                                 */

    SerialPortSettings.c_cflag = ~CRTSCTS;       /* No Hardware flow Control                         */
    SerialPortSettings.c_cflag |= CREAD | CLOCAL; /* Enable receiver,Ignore Modem Control lines       */ 


cfmakeraw(&SerialPortSettings);

tcflush(fd,TCIFLUSH);

    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);          // Disable XON/XOFF flow control both i/p and o/p
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // Non Cannonical mode                           
    SerialPortSettings.c_oflag &= ~OPOST;//No Output Processing

Writing:

    char write_buffer[] = "READ?";  /* Buffer containing characters to write into port       */ 
    int  bytes_written  = 0;    /* Value for storing the number of bytes written to the port */ 

    bytes_written = write(fd,&write_buffer,sizeof(write_buffer) -1);
printf("%s written to ttyUSB0 \n",write_buffer);
    printf("%d Bytes written to ttyUSB0 \n", bytes_written);
    printf("+----------------------------------+\n\n");

    close(fd);/* Close the Serial port */

Whenever this runs, I get:

+----------------------------------+ 
|        Serial Port Write         | 
+----------------------------------+ 
ttyUSB0 Opened Successfully 
Attributes set 
READ? written to ttyUSB0 
5 Bytes written to ttyUSB0 
+----------------------------------+

But cat /dev/ttyUSB0 does not seem to see anything coming from the device. I have checked the similar question at:

Linux C Serial Port Reading/Writing

and can not find big differences in code - would that be a sign of wrong port settings or am I missing something?


Solution

  • The problem has been fixed!

    Apparently, the device was waiting for an end-line terminator, which was not available in char write_buffer[] = "READ?";

    Therefore when it received such a command, the bus would "hang" and in order for it to work again one would have to open the port again.

    This also explains why the echo "READ?" > /dev/ttyUSB0 command worked - since it automatically outputs an end-line terminator.

    I attach the working code below - thank you everyone for your commments and kind help!

      int fd;           //device file id
    //------------------------------- Opening the Serial Port -------------------------------
        fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);    // ttyUSB0 is the FT232 based USB2SERIAL Converter 
        if(fd == -1)                        // Error Checking 
        printf("Error while opening the device\n");
    //---------- Setting the Attributes of the serial port using termios structure ---------
        struct termios SerialPortSettings;  // Create the structure                          
        tcgetattr(fd, &SerialPortSettings); // Get the current attributes of the Serial port
    // Setting the Baud rate
      cfsetispeed(&SerialPortSettings,B19200); // Set Read  Speed as 19200                       
        cfsetospeed(&SerialPortSettings,B19200); // Set Write Speed as 19200                       
    
        SerialPortSettings.c_cflag &= ~PARENB;   // Disables the Parity Enable bit(PARENB),So No Parity   
        SerialPortSettings.c_cflag &= ~CSTOPB;   // CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit 
        SerialPortSettings.c_cflag &= ~CSIZE;    // Clears the mask for setting the data size             
        SerialPortSettings.c_cflag |=  CS8;      // Set the data bits = 8                                 
        SerialPortSettings.c_cflag &= ~CRTSCTS;       // No Hardware flow Control                         
        SerialPortSettings.c_cflag |= CREAD | CLOCAL; // Enable receiver,Ignore Modem Control lines        
        SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);  // Disable XON/XOFF flow control both i/p and o/p 
        SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // Non Cannonical mode 
        SerialPortSettings.c_oflag &= ~OPOST;//No Output Processing
    // Setting Time outs 
        SerialPortSettings.c_cc[VMIN] = 13; // Read at least 10 characters 
        SerialPortSettings.c_cc[VTIME] = 0; // Wait indefinetly  
    
        if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) // Set the attributes to the termios structure
        printf("Error while setting attributes \n");
        //------------------------------- Read data from serial port -----------------------------
    
        char read_buffer[32];   // Buffer to store the data received              
        int  bytes_read = 0;    // Number of bytes read by the read() system call 
      int bytes_written = 0;  // Number of bytes written
        int i = 0;
    
        tcflush(fd, TCIFLUSH);   // Discards old data in the rx buffer            
    //Device intialization
    
      char write_buffer[]="READ? \n ";
      bytes_written=write(fd,&write_buffer,sizeof(write_buffer));
    
    
      bytes_read = read(fd,&read_buffer,32); // Read the data                   
    
        for(i=0;i<13;i++)    //printing only the needed characters
        printf("%c",read_buffer[i]);
        close(fd); // Close the serial port