1

I am using the RS-232 lines on my Pi to communicate with a laser range finder. I have tested the communication between the two using minicom at a baud rate of 19200(because that is the baud rate of the LRF and can't be changed), and it works fine. Although writing down to the LRF any commands(which consists of a single character and pressing 'enter') can take several attempts to take affect, communication in both directions works great.

However, when I start programming in C code to read and write with the RS-232, it half works. Here is the code I am using:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

//SETUP UART0

int main(){
    int uart0_filestream = -1;
    int loop;
    int i;
    int isError=1, rx_length;
    unsigned char rx_buffer[256];
    useconds_t micro=3000;

    uart0_filestream = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY | O_NDELAY);

    if(uart0_filestream == -1)
        printf("ERROR: Unable to open UART\n\n");
    else
        printf("UART open\n\n");

    struct termios options;

    tcgetattr(uart0_filestream, &options);
    options.c_cflag = B19200 | CS8 | CLOCAL | CREAD;
    options.c_iflag = IGNPAR | ICRNL;
    options.c_oflag = 0;
    options.c_lflag = 0;

    tcflush(uart0_filestream, TCIFLUSH);
    tcsetattr(uart0_filestream, TCSANOW, &options);

    unsigned char tx_buffer[20];
    unsigned char *p_tx_buffer;

    p_tx_buffer = &tx_buffer[0];
    *p_tx_buffer++ = 'o';
    *p_tx_buffer++ = '\n';

    /*
    if(uart0_filestream != -1){
        for(i = 0; i<100; i++){
            int count = write(uart0_filestream, &tx_buffer[0], (p_tx_buffer - &tx_buffer[0]));
            if(count < 0)
                printf("\n\nERROR: No bytes written\n\n");
            else
                printf("%i bytes written: %s\n", (p_tx_buffer - &tx_buffer[0]), tx_buffer);
        }
    }
    */
    if(uart0_filestream != -1){
        for(i=0; i<50; ){
            rx_length = read(uart0_filestream, (void*)rx_buffer, 255);

            if(rx_length > 0){
                printf("rx_lentgh = %i:\t ", rx_length);
                for(loop=0; loop<30; loop++){
                    //check for NULL and new line for easier readability
                    if(rx_buffer[loop] == NULL)
                        rx_buffer[loop] = '$';
                    if(rx_buffer[loop] == '\n')
                        rx_buffer[loop] = '%';
                    printf("%c", rx_buffer[loop]);
                }
                printf("\n");
                i++;
            }
        }
    }

    close(uart0_filestream);
}

When I try to read from the device, it always returns an error. I started looping to see if continually reading gave different results. Of the 100 tries, typically 4-5 return data, all the rest [i]rx_length[/i] comes back -1. The data returned should look like:

COUNTS:0000

where the number depends on the distance the LRF is measuring. But instead, I get output like this:

rx_lentgh = 16: jRþ$COUNTS:0000%$$$$$$$$$$$$
rx_lentgh = 8: %$COUNTSTS:0000%$$$$$$$$$$$$
rx_lentgh = 16: :0142%%$COUNTS:0$$$$$$$$$$$$
rx_lentgh = 8: 000%%$COCOUNTS:0$$$$$$$$$$$$
rx_lentgh = 16: UNTS:0142%%$COUN$$$$$$$$$$$$
rx_lentgh = 24: TS:0142%%$COUNTS:0000%%$$$$$
rx_lentgh = 8: COUNTS:0%$COUNTS:0000%%$$$$$
rx_lentgh = 16: 142%%$COUNTS:000:0000%%$$$$$
rx_lentgh = 16: 0%%$COUNTS:0142%:0000%%$$$$$
rx_lentgh = 8: %$COUNTSTS:0142%:0000%%$$$$$
rx_lentgh = 8: :0000%%$TS:0142%:0000%%$$$$$
rx_lentgh = 8: COUNTS:0TS:0142%:0000%%$$$$$
rx_lentgh = 24: 142%%$COUNTS:0142%%$COUN$$$$
rx_lentgh = 8: TS:0000%UNTS:0142%%$COUN$$$$
rx_lentgh = 16: %$COUNTS:0000%%$2%%$COUN$$$$
rx_lentgh = 8: COUNTS:0:0000%%$2%%$COUN$$$$
rx_lentgh = 16: 142%%$COUNTS:0002%%$COUN$$$$
rx_lentgh = 8: 0%%$COUNUNTS:0002%%$COUN$$$$
rx_lentgh = 16: TS:0142%%$COUNTS2%%$COUN$$$$
rx_lentgh = 8: :0000%%$%$COUNTS2%%$COUN$$$$
rx_lentgh = 16: COUNTS:0142%%$CO2%%$COUN$$$$
rx_lentgh = 8: UNTS:000142%%$CO2%%$COUN$$$$
rx_lentgh = 24: 0%%$COUNTS:0142%%$COUNTS$$$$
rx_lentgh = 16: :0000%%$COUNTS:0%$COUNTS$$$$
rx_lentgh = 24: 142%%$COUNTS:0142%%$COUN$$$$
rx_lentgh = 8: TS:0000%UNTS:0142%%$COUN$$$$
rx_lentgh = 16: %$COUNTS:0142%%$2%%$COUN$$$$

**The above is edited in my code for readability. A NULL character is replaced with '$' and a '\n' is replaced with '%'

You can see that every time it gets data, it at least gets part of a good read, and occasionally the whole thing. But there is a lot of junk in there. You can see in my code I have filtered out all reads that returned in an error. It would probably take over 1000 reads to get this many "good" reads. I really think it has to do with timing, but even if it was timing, wouldn't shouldn't I still be getting [i]some[/i] data back?

Writing has the same issue. A single write does nothing. Looping the write code 100 times may end up getting the code down to the LRF, but the LRF pretty much doesn't work at all after running that code and I haven't to cut power to get it to work and view data in minicom again.

The LRF has can send packets at 200Hz or 10Hz, depending on the mode. All data retrieved above was done with the LRF sending packets at 200Hz.

Any help at all would be greatly appreciated! I have been working on this for several weeks between my other classes and work.

jrpharis
  • 81
  • 1
  • 1
  • 11
  • Before you start suspecting the software, have you checked the physical connections? Maybe there's a bad connection somewhere that's messing up your communications. – tangrs Sep 28 '13 at 23:18
  • Well, I consistently get good communication using the terminal application minicom. I can run minicom, see good data, run my good and get junk. Then run minicom again and get good data. So I really don't think it's the connections. – jrpharis Sep 28 '13 at 23:41

2 Answers2

1

There are several issues with your code.


uart0_filestream = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY | O_NDELAY);

You have setup the port for non-blocking I/O.
This is probably not what you want. Add the following after the return code checking:

fcntl(uart0_filestream, F_SETFL, 0);

in order to configure blocking I/O.


if(uart0_filestream == -1)
    printf("ERROR: Unable to open UART\n\n");

When there is a fatal error the program should exit, rather than continue on.
You also need to examine the value of errno when a syscall returns -1.


tcgetattr(uart0_filestream, &options);
  ...
tcsetattr(uart0_filestream, TCSANOW, &options);

Return codes from syscalls should always be checked.


options.c_cflag = B19200 | CS8 | CLOCAL | CREAD;
options.c_iflag = IGNPAR | ICRNL;
options.c_oflag = 0;
options.c_lflag = 0;

This is improper modification of termios members.
Only proper macros and bit-wise operations should be performed. Refer to the Posix Guide of Serial Programing.
You have disabled canonical input processing, which is probably not what you want to do.
The input you are trying to read is ASCII text with line termination, so use canonical mode to have the system parse for the end of each line.
You now have the port configured for raw mode, which is intended for binary data or to ignore ASCII control characters.

For sample code to configure canonical mode see this answer.


        rx_length = read(uart0_filestream, (void*)rx_buffer, 255);

Again, you need to examine the value of errno when a syscall return -1.
When read() returns -1 for your code, errno is probably EAGAIN to indicate that there was no data available.


If blocking I/O is specified and canonical I/O instead of raw I/O, then each read() will return with a complete line of input.

Community
  • 1
  • 1
sawdust
  • 16,103
  • 3
  • 40
  • 50
  • The non-blocking and non-canonical input did! I guess I misunderstood what canonical and non-canonical input were. Except now, once I've done 5-6 reads on the device, it goes into a weird state and stops outputting, where only a power cycle will fix it. I find that odd, since I can run minicom as long as I want and no issues. – jrpharis Oct 03 '13 at 02:01
  • That probably deserves a new question that shows your cleaned-up code and a better description of the symptoms, e.g. what is "it" in "it goes into a weird state". See edited answer for a short explanation of canonical versus raw modes. – sawdust Oct 03 '13 at 05:23
  • Thanks, I have created a new question here with cleaner code per your suggestions: [New issues](http://stackoverflow.com/questions/19187418/rs-232-communication-with-rpi) – jrpharis Oct 04 '13 at 21:15
0

Suggest OP is mis-interpreting the data - it looks fairly good.

Recommend re-write inner loop

// for(loop=0; loop<30; loop++)
for(loop=0; loop<rx_length; loop++)

After doing this, the result should appear much better. The trick is that the read() sample occurs asynchronously to the arriving data such that only portion of the message are read. read() does not know when the packet ended. It returns when its full, error or when there is a no more available data at that moment. There is no synchronization between the read() and the arrival of the packet end. Re-integration of the message is needed.

Synchronization pseudo code

i = 0;
length = 0;
forever() {   
  do {
    i += length;
    length = read(&buffer[i])
    if (length means bad read) handle error;
    search buffer from i to (i+length) for End-of-packet
  } while (end-of-packet not found and buffer not too full)
  Use buffer from start to end-of-packet.
  length = i+length-end-of-packet-index
  memmove(&buffer[0], &buffer[end-of-packet-index+1], length);
  i = 0;
}

You could check into other read()-like functions that read until a timeout. (Maybe a different open() option too?

Other minor points

  1. More comments in the options.c_cflag to explain options.

  2. "rx_length" vs "rx_lentgh".

  3. rx_buffer[loop] == NULL is bad form. rx_buffer[loop] is a char. NULL is a pointer. Use rx_buffer[loop] == '\0'.

  4. For debug purposes, consider

.

// printf("%c", rx_buffer[loop]);
if (isprint(rx_buffer[loop])) {
   printf("%c", rx_buffer[loop]);
}
else {
   printf("(%02hhX)", rx_buffer[loop]);
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256