2
votes

I'm attempting to read from my serial port using the following C code. I can successfully write to a listening computer (yay!) but the read throws Error (Code 11 - Resource temporarily unavailable). I have also noticed my messages/dmesg logs don't have any information regarding faults,etc. So that's good.

//A bunch of INCLUDES exist here....the the code

int fd=0;
int status=0;
int running=1;
char buffer[100];
char message[7];

void main(){
    fd = 1;

    fd=open("/dev/ttyM0",O_RDWR | O_NOCTTY);

    if(fd == -1)
    {
        perror("open_port: Unable to open /dev/ttys0");
    }
    else
    {
        while(running<20)
        {
            sprintf(message,"Test%d\r",running);
            status=write(fd,message,6);

            if(status<0)
            {
                printf("Error Writing. Status=%d\n %s\n",errno, strerror(errno));
            }
            status=read(fd,buffer,8);  //This throws an error(11). My connected device is writing "Testing/r"

            if(status<0)
            {
                printf("Error Reading. Status=%d \n%s\n",errno, strerror(errno));
                //close(fd);
                running=running+1;
            }
            else
            {
                printf("%s\n\r",buffer);
            }
            sleep(2);
        }
        close(fd);
    }

} //END MAIN

These are my serial settings for my port. I'm attempting to read/write at 9600 8bit, No parity, 1 stop bit. I think my settings are correct.

sudo stty -a -F /dev/ttyM0 
speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany
-imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

Any help would be very much appreciated. Thank you!

3
yea, I read this post before and I agree that data isn't going to always be available when using the O_NDELAY flag but I've handled this by running my code a loop. The example you point to only calls read once. - MateoIO
@Craig -- Read the man page for open(): O_NDELAY is the same as O_NONBLOCK. So you have opened the serial port for nonblocked I/O. You have to anticipate and properly handle the EAGAIN return code. If you don't know how to handle EAGAIN, then the simple solution is to perform blocked I/O. IMO if all you're going to do is loop waiting for data to read, then you should be using a blocked read. - sawdust
read() does not store a null-terminated text string (unless that's what is actually transmitted and received), so the subsequent printf() will probably output spurious characters. - sawdust
Looks like termios is setup for canonical input (based on the icanon in the stty output). Verify that your remote device is actually sending a CR or LF control character to terminate the line. Your comment in the code indicates that it is not (i.e. "/r" is not a carriage return). - sawdust

3 Answers

2
votes

You have a buffer overflow here:

sprintf(message,"Test%d\r",running);

since message is declared as:

char message[6];

message needs to be at least 7 characters in size if it's going to hold a 6 character string, due to the need for a '\0' terminator.

There may well be other problems too, but you should fix this and see if it makes any difference.

2
votes

You're using the O_NDELAY option in your open call. This make the file descriptor non-blocking. This means that if you do a read and there's no data available, the read call will return EAGAIN which is the error you're seeing.

For the moment, you can remove the O_NDELAY from open. In the future, you should probably make it non-blocking again and use either select or poll to determine when you can read.

1
votes

With O_NDELAY gone the program just sits there waiting for input

Looks like termios is setup for canonical input (based on the icanon in the stty output). In canonical (aka cooked) mode, the characters received from the serial port are processed before being made available to the user program using read().

Per the Linux man page:

In canonical mode:

  • Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).

Your termios also has icrnl set, which means that a carriage return is translated to newline on input (unless igncr is set, which is not since it has a preceding hyphen). Both eol and eol2 are undefined (which are the default values).

So for your setup, an end-of-line is defined as a newline or a carriage return (or cntl-D at the start of the line). Verify that your remote device is actually sending a CR or LF control character to terminate the line. Your comment in the code indicates that it is not (i.e. "/r" is not a carriage return).


To properly use the text returned by read() as a string, set the request for one less than the allocated buffer size (to ensure room for appending a null terminator). Then after a good return, use the returned byte count as the index to store the string terminator.

        status = read(fd, buffer, sizeof(buffer) - 1);  
        if (status < 0) {
            /* handle error condition */
        } else {
            buffer[status] = '\0';
            printf("%s\n\r", buffer);
        }