问题
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.
回答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.
回答2:
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
More comments in the
options.c_cflag
to explain options."rx_length" vs "rx_lentgh".
rx_buffer[loop] == NULL
is bad form.rx_buffer[loop]
is achar
.NULL
is a pointer. Userx_buffer[loop] == '\0'
.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]);
}
来源:https://stackoverflow.com/questions/19072518/raspberrypi-rs-232-trouble