2

I have an instrument which I need to talk to by RS232. I am using C and following is the code. The problem is that, when I try to read field value (reading from meter) which is 11 byte long, one read command is reading only 8 bytes and subsequently I have to issue another read command which gives me final 3 bytes. Finally I am concatenating both read buffers to make final meaningful value.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#define MAXWAIT         30
#define BAUDRATE        B9600 
#define TESLAMETER      "/dev/ttyS0"
#define _POSIX_SOURCE   1 /* POSIX compliant source */
#define FALSE           0
#define TRUE            1
#define NOREAD          255

volatile int STOP = FALSE;
int fd;
struct termios oldtp, newtp;

int openComPort(void)
{
    fd = open(TESLAMETER, O_RDWR | O_NOCTTY |O_NDELAY );
    if (fd <0)
    {
        perror(TESLAMETER);     
        return fd;   
    }
    else
        fcntl(fd,F_SETFL,0);
    tcgetattr(fd,&oldtp); /* save current serial port settings */
    bzero(&newtp, sizeof(newtp));
    newtp.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
    newtp.c_iflag = IGNPAR | ICRNL;
    newtp.c_oflag = 0;
    newtp.c_lflag = 0;//ICANON;
    newtp.c_cc[VINTR]    = 0;     /* Ctrl-c */
    newtp.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
    newtp.c_cc[VERASE]   = 0;     /* del */
    newtp.c_cc[VKILL]    = 0;     /* @ */
    newtp.c_cc[VEOF]     = 4;     /* Ctrl-d */
    newtp.c_cc[VTIME]    = 1;     /* inter-character timer unused, 0.5 seconds read timeout */
    newtp.c_cc[VMIN]     = 0;     /* blocking or non blocking read until 1 character arrives */

    tcflush(fd, TCIFLUSH);
    tcsetattr(fd,TCSANOW,&newtp);
    return fd;
}
float readMagField()
{
    unsigned char cmd[]="FA0\r"; // Read Field
    char buff2[11] = {0x00};
    char buff3[11] = {0x00};
    float fieldFloat = 0.00;
    int n_written= 0, spot = 0, res;
    do
    {
        n_written = write( fd, &cmd[spot], 1 );
        spot += n_written;
    } while (cmd[spot-1] != '\r' && n_written > 0);
    if (n_written < 0)
    {
            printf("write() of 4 bytes failed!\n");
        return FALSE;
    }
    else
    {
        //printf("Field Read Command sent successfully %d\n",n_written);
        res = read(fd,buff2,11);   // Reads 8 bytes
        res = read(fd,buff3,11);   // Reads remaining 3 bytes
        fieldFloat = atof(strcat(buff2,buff3)); // Final string of 11 bytes here
        return fieldFloat;
    }
}

Is there something that I am doing or setting wrong? Because, I can read the complete set of characters in one go using Python serial module, but not in C. I am working on Ubuntu 12.04 LTS.

Sagar Jain
  • 7,475
  • 12
  • 47
  • 83
Ashish Sharma
  • 148
  • 3
  • 14
  • You have opened the device for *nonblocking* mode by specifying **O_NDELAY**. If the data are not already available for reading, you've told the system that you don't want to wait for it. Also, you have crudely setup noncanonical mode, and because this is a character device, at this level there is no concept of message packet or record. You're doing the things to ensure that **read()** treats the input as bytes of raw data. **Perhaps you should be using blocking canonical reads?** – sawdust Sep 17 '15 at 20:16
  • Only by good luck does `fieldFloat = atof(strcat(buff2,buff3));` not blow up on you. **read()** simply return bytes; it does not return a null-terminated string (unless that is what was transmitted and received). – sawdust Sep 17 '15 at 20:22
  • In some cases, I actually don't want to make read a blocking one. Such as, if device is not connected, then program will keep wait but nothing would be received. – Ashish Sharma Sep 18 '15 at 07:08
  • Apparently I misread the code. The serial terminal is opened for nonblocking mode, but then changed to blocking mode with a `fcntl(fd,F_SETFL,0);` (and preceded with a superfluous `else`). Therefore the noncanonical parameters for VMIN and VTIME are in effect, and **read()** s are timed for 0.1 seconds. (The comments you copied no longer match the edited code.) If that only fetches 8 of the expected 11 bytes, then try increasing VTIME to `2`. **Or** use canonical mode to read the line of text. – sawdust Mar 22 '21 at 04:48

1 Answers1

2

read() may return without reading the specified length.

read(2) - Linux manual page

RETURN VALUE

   On success, the number of bytes read is returned (zero indicates end
   of file), and the file position is advanced by this number.  It is
   not an error if this number is smaller than the number of bytes
   requested; this may happen for example because fewer bytes are
   actually available right now (maybe because we were close to end-of-
   file, or because we are reading from a pipe, or from a terminal), or
   because read() was interrupted by a signal.  See also NOTES.

How about retrying until the desired length of data have been read?

ssize_t read2(int fd, void *buf, size_t count) {
    ssize_t read_length = 0;
    while (read_length < count) {
        ssize_t delta = read(fd, buf + read_length, count - read_length);
        if (delta == -1) return -1;
        read_length += delta;
    }
    return read_length;
}
MikeCAT
  • 73,922
  • 11
  • 45
  • 70
  • I can use this only when I am sure that I should receive something. So, I accept this. One more thing, how should I return if nothing is sent from the device such as in the condition, it is not connected. I want to use this condition to check for its presence? – Ashish Sharma Sep 18 '15 at 07:10
  • 1
    This code should be avoided if the serial connection was opened with the `O_NDELAY` flag. – Codo Mar 21 '21 at 21:44