I am using termios
in C to write a simple program to write to a serial port and read the returned data. The communication with the device on the serial line terminates with a carriage return. The program is simple and currently looks like:
我在C中使用termios编写一个简单的程序来写入串口并读取返回的数据。与串行线上设备的通信以回车符结束。该程序很简单,目前看起来像:
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
struct termios s_alicat;
int tty_fd, count;
// Ports connected in USB as sudo
if ((tty_fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
{
printf("Line failed to open with %d\n", tty_fd);
return -1;
}
else
{
printf("fd is %d\n", tty_fd);
}
s_alicat.c_cflag = B19200 | CS8 | CREAD | CLOCAL;
//No parity 8N1:
s_alicat.c_cflag &= ~PARENB;
s_alicat.c_cflag &= ~CSTOPB;
s_alicat.c_cflag &= ~CSIZE;
s_alicat.c_iflag = IGNPAR | ICRNL; // Ignore parity errors
//Disable hardware flow control
s_alicat.c_cflag &= ~CRTSCTS;
//Disable software flow control
s_alicat.c_iflag &= ~(IXON | IXOFF | IXANY);
//Raw output
s_alicat.c_oflag &= ~OPOST;
//Raw input
s_alicat.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tcflush(tty_fd, TCIFLUSH);
tcsetattr(tty_fd, TCSANOW, &s_alicat);
unsigned char transmit[2] = "C\r";
if ((count = write(tty_fd, &transmit, 2)) < 0)
{
printf("Failed to write to device");
}
printf("Transmited %d characters\n", count);
usleep(500000);
unsigned char receive[255];
if ((count = read(tty_fd, &receive, 255) < 0))
{
printf("Error receiving text %d", count);
}
else
{
if (count == 0)
{
printf("No data read in...\n");
}
else
{
printf("%s", receive);
}
}
printf("Closting port...\n");
close(tty_fd);
return 0;
}
So:
- The port opens correctly
- I am able to write the data (can physically see that it is going across the line via LEDs that light up when data is transmitted and received)
- The read returns with 0 characters
端口正确打开
我能够写入数据(可以通过发送和接收数据时点亮的LED实际看到它正在通过线路)
读取返回0个字符
If I send the same command (C\r
) through another program set up with 19.2, 8N1, no flow control, I get the following string ( or something very similar) back
如果我通过另一个设置为19.2,8N1的程序发送相同的命令(C \ r),没有流量控制,我会得到以下字符串(或非常类似的东西)
C\s+012.05\s+031.73\s+000.01\s+000.01\s010.24\s\s\s\s\sAir\r
So, what am I doing wrong here? Does this have something to do with the fact that the IO is carriage return terminated? Or is my configuration incorrect?
那么,我在这里做错了什么?这是否与IO回车终止这一事实有关?或者我的配置不正确?
EDIT: So, it appears that if I watch the character device (/dev/ttyUSB0
) I can actually see the data coming back - see snap shot below. So, it looks like my issue is in reading into and getting information from the read buffer.
编辑:所以,看来,如果我观看角色设备(/ dev / ttyUSB0)我实际上可以看到数据回来 - 请参阅下面的快照。所以,看起来我的问题是从读缓冲区读取和获取信息。
2 个解决方案
#1
3
Or is my configuration incorrect?
或者我的配置不正确?
Yes.
The problem is that your program uses non-blocking mode
是。问题是您的程序使用非阻塞模式
open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY)
and combines that with non-canonical mode
并将其与非规范模式相结合
s_alicat.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
even though you state that the input are lines terminated "with (a) carriage return."
即使您声明输入的行是“(a)回车”。
A "read returns with 0 characters" is predictable and normal for non-blocking raw reads (like your configuration) whenever there is simply no data available. See this answer for the full details.
只要没有可用数据,“无法阻塞原始读取(如配置)”的“读取返回0个字符”是可预测的和正常的。有关详细信息,请参阅此答案。
To correct your program, use blocking mode.
Insert the following statement after the file descriptor has been obtained:
要更正您的程序,请使用阻止模式。获取文件描述符后插入以下语句:
fcntl(tty_fd, F_SETFL, 0); /* set blocking mode */
See this for an explanation.
请参阅此处以获得解释。
As for the termios configuration, your program has a serious bug: it uses the termios structure s_alicat uninitialized.
The proper method is to use tcgetattr().
Refer to Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems .
至于termios配置,你的程序有一个严重的错误:它使用未初始化的termios结构s_alicat。正确的方法是使用tcgetattr()。请参阅正确设置终端模式和POSIX操作系统的串行编程指南。
#include <errno.h>
#include <string.h>
...
if (tcgetattr(tty_fd, &s_alicat) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&s_alicat, B19200);
cfsetispeed(&s_alicat, B19200);
s_alicat.c_cflag |= (CLOCAL | CREAD);
s_alicat.c_cflag &= ~CSIZE;
s_alicat.c_cflag |= CS8; /* 8-bit characters */
s_alicat.c_cflag &= ~PARENB; /* no parity bit */
s_alicat.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
s_alicat.c_iflag |= ICRNL; /* CR is a line terminator */
s_alicat.c_iflag |= IGNPAR; // Ignore parity errors
// no flow control
s_alicat.c_cflag &= ~CRTSCTS;
s_alicat.c_iflag &= ~(IXON | IXOFF | IXANY);
// canonical input & output
s_alicat.c_lflag |= ICANON;
s_alicat.c_lflag &= ~(ECHO | ECHOE | ISIG);
s_alicat.c_oflag |= OPOST;
if (tcsetattr(tty_fd, TCSANOW, &s_alicat) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
Additional bugs in your code include use of pointer to array addresses (i.e. address of address) when the array address would suffice.
代码中的其他错误包括在数组地址足够时使用指向数组地址的指针(即地址地址)。
write(tty_fd, &transmit, 2)
read(tty_fd, &receive, 255)
should simply be respectively
应该只是分别
write(tty_fd, transmit, 2)
read(tty_fd, receive, 255)
The read() syscall does not return or store a string, yet your program assumes it does.
The code (with the precedence bug corrected) should be:
read()系统调用不返回或存储字符串,但是您的程序假设它。代码(纠正优先级错误)应该是:
if ((count = read(tty_fd, receive, sizeof(receive) - 1)) < 0) {
printf("Error receiving text %s\n", strerror(errno));
} else {
receive[count] = 0; /* terminate string */
printf("Received %d: \"%s\"\n", count, receive);
}
Note that the read request length is one-less-than the buffer size to reserve space for a terminating null byte.
请注意,读取请求长度比缓冲区大小小一个,以便为终止空字节保留空间。
ADDENDUM
Your code has a precedence/parentheses bug, which carried over into my code. The offending statement is:
您的代码有一个优先级/括号错误,它会延续到我的代码中。违规声明是:
if ((count = read(tty_fd, &receive, 255) < 0))
The assignment to variable count should be the return code from the read() syscall, rather than the evaluation of the logical expression read() < 0
.
Without proper parentheses, the comparison is performed first since the less-than operator has higher precedence than the assignment operator.
This bug causes count, when there's a good read (i.e. a positive, non-zero return code), to always be assigned the value 0 (i.e. the integer value for false).
变量计数的赋值应该是read()系统调用的返回代码,而不是逻辑表达式read()<0的评估。如果没有正确的括号,则首先执行比较,因为小于运算符的优先级高于赋值运算符。当存在良好的读取(即,正的非零返回码)时,该错误导致计数总是被赋予值0(即,假的整数值)。
The revised code from this answer merged with your code was tested, and confirmed to function as expected with text terminated with a carriage return.
来自此答案的修订代码与您的代码合并进行了测试,并确认按预期运行,文本以回车符结束。
#2
0
@sawdust - thanks for your help. I will post my working code here with a few initial comments.
@sawdust - 谢谢你的帮助。我将在这里发布我的工作代码,并附上一些初步评论。
The problem is that your program uses non-blocking mode
问题是您的程序使用非阻塞模式
This is actually not a problem and is exactly what I want. I don't want reads to block as this could cause the program to hang if the device is not responding. This code is just for testing whether my serial interface is good. So, setting the flags to 0 with fcntl(tty_fd, F_SETFL, 0)
is what I don't want to do.
这实际上不是问题,而且正是我想要的。我不希望读取被阻止,因为如果设备没有响应,这可能导致程序挂起。此代码仅用于测试我的串行接口是否良好。因此,使用fcntl(tty_fd,F_SETFL,0)将标志设置为0是我不想做的。
The assignment to variable count should be the return code from the
read()
syscall变量count的赋值应该是read()系统调用的返回码
I think that I am following you here but the wording is odd. Yes, the parentheses were improperly placed - thank you for pointing that out. But, by "return code" I assume that you mean -1 or the number of bytes received? Based on your updated response I am assuming so.
我想我在这里跟着你,但措辞很奇怪。是的,括号放置不当 - 谢谢你指出了这一点。但是,通过“返回代码”,我假设你的意思是-1或接收的字节数?根据您更新的回复我假设如此。
So, here is the final code. I believe that I incorporated the feedback that you provided into it. Feel free to provide more if you see anything odd. The return from the function when this run looks like
所以,这是最终的代码。我相信我将您提供的反馈纳入其中。如果你看到任何奇怪的东西,请随意提供更多。此运行时从函数返回
root@cirrus /h/m/D/s/F/C/c/src# ./main
fd is 3
count is 49
String is C +012.17 +030.85 +000.00 +000.00 010.24 Air
Closing port...
The code:
#include <stdlib.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define PORT "/dev/ttyUSB0"
int main(void)
{
struct termios s_alicat;
int tty_fd, count;
char receive[255], transmit[2];
// Ports connected in USB as sudo
if ((tty_fd = open(PORT, O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
{
printf("Line failed to open with %d\n", tty_fd);
return -1;
}
else
{
printf("fd is %d\n", tty_fd);
}
if (tcgetattr(tty_fd, &s_alicat) < 0)
{
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&s_alicat, B19200);
cfsetispeed(&s_alicat, B19200);
// Set up receiver and set to local mode
s_alicat.c_cflag |= (CLOCAL | CREAD | CS8);
s_alicat.c_iflag |= IGNPAR | ICRNL; // Ignore parity errorss
tcflush(tty_fd, TCIFLUSH); //discard file information not transmitted
if (tcsetattr(tty_fd, TCSANOW, &s_alicat) != 0)
{
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
// Clear the port before kicking off communications
strcpy(transmit, "\r\r");
write(tty_fd, transmit, 2);
strcpy(transmit, "C\r");
if ((count = write(tty_fd, transmit, 2)) < 0)
{
printf("Failed to write to device");
}
int j = 0;
count = 0;
/* Attempt to read data at most 3 times if there is no data
* coming back.
*/
while (count == 0 && j < 3)
{
usleep(100000);
if ((count = read(tty_fd, receive, sizeof(receive) - 1)) < 0)
{
printf("Error receiving text %d", count);
}
else
{
printf("count is %d\n", count);
receive[count] = 0;
printf("String is %s", receive);
}
j++;
}
printf("Closing port...\n");
int p = 0;
if ((p = close(tty_fd)) < 0)
{
printf("Port failed to close %d\n", p);
return -1;
}
return 0;
}
Edit
Added explicit setting of baud rate via cfsetospeed
and cfsetispeed
.
通过cfsetospeed和cfsetispeed添加了波特率的显式设置。
Another Edit
Got rid of redundant tcgetattr
call.
摆脱了多余的tcgetattr调用。
#1
3
Or is my configuration incorrect?
或者我的配置不正确?
Yes.
The problem is that your program uses non-blocking mode
是。问题是您的程序使用非阻塞模式
open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY)
and combines that with non-canonical mode
并将其与非规范模式相结合
s_alicat.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
even though you state that the input are lines terminated "with (a) carriage return."
即使您声明输入的行是“(a)回车”。
A "read returns with 0 characters" is predictable and normal for non-blocking raw reads (like your configuration) whenever there is simply no data available. See this answer for the full details.
只要没有可用数据,“无法阻塞原始读取(如配置)”的“读取返回0个字符”是可预测的和正常的。有关详细信息,请参阅此答案。
To correct your program, use blocking mode.
Insert the following statement after the file descriptor has been obtained:
要更正您的程序,请使用阻止模式。获取文件描述符后插入以下语句:
fcntl(tty_fd, F_SETFL, 0); /* set blocking mode */
See this for an explanation.
请参阅此处以获得解释。
As for the termios configuration, your program has a serious bug: it uses the termios structure s_alicat uninitialized.
The proper method is to use tcgetattr().
Refer to Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems .
至于termios配置,你的程序有一个严重的错误:它使用未初始化的termios结构s_alicat。正确的方法是使用tcgetattr()。请参阅正确设置终端模式和POSIX操作系统的串行编程指南。
#include <errno.h>
#include <string.h>
...
if (tcgetattr(tty_fd, &s_alicat) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&s_alicat, B19200);
cfsetispeed(&s_alicat, B19200);
s_alicat.c_cflag |= (CLOCAL | CREAD);
s_alicat.c_cflag &= ~CSIZE;
s_alicat.c_cflag |= CS8; /* 8-bit characters */
s_alicat.c_cflag &= ~PARENB; /* no parity bit */
s_alicat.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
s_alicat.c_iflag |= ICRNL; /* CR is a line terminator */
s_alicat.c_iflag |= IGNPAR; // Ignore parity errors
// no flow control
s_alicat.c_cflag &= ~CRTSCTS;
s_alicat.c_iflag &= ~(IXON | IXOFF | IXANY);
// canonical input & output
s_alicat.c_lflag |= ICANON;
s_alicat.c_lflag &= ~(ECHO | ECHOE | ISIG);
s_alicat.c_oflag |= OPOST;
if (tcsetattr(tty_fd, TCSANOW, &s_alicat) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
Additional bugs in your code include use of pointer to array addresses (i.e. address of address) when the array address would suffice.
代码中的其他错误包括在数组地址足够时使用指向数组地址的指针(即地址地址)。
write(tty_fd, &transmit, 2)
read(tty_fd, &receive, 255)
should simply be respectively
应该只是分别
write(tty_fd, transmit, 2)
read(tty_fd, receive, 255)
The read() syscall does not return or store a string, yet your program assumes it does.
The code (with the precedence bug corrected) should be:
read()系统调用不返回或存储字符串,但是您的程序假设它。代码(纠正优先级错误)应该是:
if ((count = read(tty_fd, receive, sizeof(receive) - 1)) < 0) {
printf("Error receiving text %s\n", strerror(errno));
} else {
receive[count] = 0; /* terminate string */
printf("Received %d: \"%s\"\n", count, receive);
}
Note that the read request length is one-less-than the buffer size to reserve space for a terminating null byte.
请注意,读取请求长度比缓冲区大小小一个,以便为终止空字节保留空间。
ADDENDUM
Your code has a precedence/parentheses bug, which carried over into my code. The offending statement is:
您的代码有一个优先级/括号错误,它会延续到我的代码中。违规声明是:
if ((count = read(tty_fd, &receive, 255) < 0))
The assignment to variable count should be the return code from the read() syscall, rather than the evaluation of the logical expression read() < 0
.
Without proper parentheses, the comparison is performed first since the less-than operator has higher precedence than the assignment operator.
This bug causes count, when there's a good read (i.e. a positive, non-zero return code), to always be assigned the value 0 (i.e. the integer value for false).
变量计数的赋值应该是read()系统调用的返回代码,而不是逻辑表达式read()<0的评估。如果没有正确的括号,则首先执行比较,因为小于运算符的优先级高于赋值运算符。当存在良好的读取(即,正的非零返回码)时,该错误导致计数总是被赋予值0(即,假的整数值)。
The revised code from this answer merged with your code was tested, and confirmed to function as expected with text terminated with a carriage return.
来自此答案的修订代码与您的代码合并进行了测试,并确认按预期运行,文本以回车符结束。
#2
0
@sawdust - thanks for your help. I will post my working code here with a few initial comments.
@sawdust - 谢谢你的帮助。我将在这里发布我的工作代码,并附上一些初步评论。
The problem is that your program uses non-blocking mode
问题是您的程序使用非阻塞模式
This is actually not a problem and is exactly what I want. I don't want reads to block as this could cause the program to hang if the device is not responding. This code is just for testing whether my serial interface is good. So, setting the flags to 0 with fcntl(tty_fd, F_SETFL, 0)
is what I don't want to do.
这实际上不是问题,而且正是我想要的。我不希望读取被阻止,因为如果设备没有响应,这可能导致程序挂起。此代码仅用于测试我的串行接口是否良好。因此,使用fcntl(tty_fd,F_SETFL,0)将标志设置为0是我不想做的。
The assignment to variable count should be the return code from the
read()
syscall变量count的赋值应该是read()系统调用的返回码
I think that I am following you here but the wording is odd. Yes, the parentheses were improperly placed - thank you for pointing that out. But, by "return code" I assume that you mean -1 or the number of bytes received? Based on your updated response I am assuming so.
我想我在这里跟着你,但措辞很奇怪。是的,括号放置不当 - 谢谢你指出了这一点。但是,通过“返回代码”,我假设你的意思是-1或接收的字节数?根据您更新的回复我假设如此。
So, here is the final code. I believe that I incorporated the feedback that you provided into it. Feel free to provide more if you see anything odd. The return from the function when this run looks like
所以,这是最终的代码。我相信我将您提供的反馈纳入其中。如果你看到任何奇怪的东西,请随意提供更多。此运行时从函数返回
root@cirrus /h/m/D/s/F/C/c/src# ./main
fd is 3
count is 49
String is C +012.17 +030.85 +000.00 +000.00 010.24 Air
Closing port...
The code:
#include <stdlib.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define PORT "/dev/ttyUSB0"
int main(void)
{
struct termios s_alicat;
int tty_fd, count;
char receive[255], transmit[2];
// Ports connected in USB as sudo
if ((tty_fd = open(PORT, O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
{
printf("Line failed to open with %d\n", tty_fd);
return -1;
}
else
{
printf("fd is %d\n", tty_fd);
}
if (tcgetattr(tty_fd, &s_alicat) < 0)
{
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&s_alicat, B19200);
cfsetispeed(&s_alicat, B19200);
// Set up receiver and set to local mode
s_alicat.c_cflag |= (CLOCAL | CREAD | CS8);
s_alicat.c_iflag |= IGNPAR | ICRNL; // Ignore parity errorss
tcflush(tty_fd, TCIFLUSH); //discard file information not transmitted
if (tcsetattr(tty_fd, TCSANOW, &s_alicat) != 0)
{
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
// Clear the port before kicking off communications
strcpy(transmit, "\r\r");
write(tty_fd, transmit, 2);
strcpy(transmit, "C\r");
if ((count = write(tty_fd, transmit, 2)) < 0)
{
printf("Failed to write to device");
}
int j = 0;
count = 0;
/* Attempt to read data at most 3 times if there is no data
* coming back.
*/
while (count == 0 && j < 3)
{
usleep(100000);
if ((count = read(tty_fd, receive, sizeof(receive) - 1)) < 0)
{
printf("Error receiving text %d", count);
}
else
{
printf("count is %d\n", count);
receive[count] = 0;
printf("String is %s", receive);
}
j++;
}
printf("Closing port...\n");
int p = 0;
if ((p = close(tty_fd)) < 0)
{
printf("Port failed to close %d\n", p);
return -1;
}
return 0;
}
Edit
Added explicit setting of baud rate via cfsetospeed
and cfsetispeed
.
通过cfsetospeed和cfsetispeed添加了波特率的显式设置。
Another Edit
Got rid of redundant tcgetattr
call.
摆脱了多余的tcgetattr调用。