在 C 中串行读取不返回任何数据
Serial read in C returning no data
我正在使用 C 中的 termios
编写一个简单的程序来写入串行端口并读取 returned 数据。与串行线路上的设备的通信以回车符 return 终止。该程序很简单,目前看起来像:
#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;
}
所以:
- 端口正确打开
- 我能够写入数据(通过发送和接收数据时点亮的 LED 可以物理地看到它正在穿过线路)
- 已读 returns 有 0 个字符
如果我通过另一个设置为 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
那么,我做错了什么?这与 IO 被回车 return 终止这一事实有关吗?还是我配置不对?
编辑:所以,如果我观看字符设备 (/dev/ttyUSB0
),我实际上可以看到返回的数据 - 请参见下面的快照。所以,看起来我的问题是读取 buffer.
并从中获取信息
Or is my configuration incorrect?
是的。
问题是你的程序使用了非阻塞模式
open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY)
并将其与非规范模式相结合
s_alicat.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
即使您声明输入是终止行“with (a) carriage return”。
A“read returns with 0 characters”是可预测的,并且对于非阻塞原始读取(如您的配置)来说是正常的,只要没有可用数据.有关完整详细信息,请参阅 this answer。
要更正您的程序,请使用阻塞模式。
在获取文件描述符后插入以下语句:
fcntl(tty_fd, F_SETFL, 0); /* set blocking mode */
有关解释,请参阅 this。
关于termios配置,你的程序有一个严重的bug:它使用了termios结构s_alicat未初始化
正确的方法是使用 tcgetattr().
参考Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems
.
#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;
}
代码中的其他错误包括在数组地址足够时使用指向数组地址的指针(即地址的地址)。
write(tty_fd, &transmit, 2)
read(tty_fd, &receive, 255)
应该就是分别
write(tty_fd, transmit, 2)
read(tty_fd, receive, 255)
read() 系统调用不 return 或存储字符串,但您的程序假定它确实如此。
代码(纠正了优先错误)应该是:
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);
}
请注意,读取请求长度比要为终止空字节保留 space 的缓冲区大小小一。
附录
您的代码有一个 precedence/parentheses 错误,该错误已转移到我的代码中。违规声明是:
if ((count = read(tty_fd, &receive, 255) < 0))
对变量 count 的赋值应该是来自 read() 系统调用的 return 代码,而不是评估逻辑表达式 read() < 0
.
如果没有适当的括号,则首先执行比较,因为小于运算符的优先级高于赋值运算符。
这个错误导致 count,当有一个好的读取(即一个正的非零 return 代码)时,总是被分配值 0(即 false 的整数值).
与您的代码合并的此答案的修订代码已经过测试,并确认可以按预期运行,文本以回车符结尾 return。
@sawdust - 感谢您的帮助。我将 post 我的 工作代码和一些初步评论。
The problem is that your program uses non-blocking mode
这实际上不是问题,而且正是我想要的。我不希望读取阻塞,因为如果设备没有响应,这可能会导致程序挂起。这段代码只是为了测试我的串口是否良好。因此,使用 fcntl(tty_fd, F_SETFL, 0)
将标志设置为 0 是我不想做的。
The assignment to variable count should be the return code from the read()
syscall
我想我是在关注你,但措辞很奇怪。是的,括号放置不当 - 感谢您指出这一点。但是,"return code" 我假设你的意思是 -1 或接收到的字节数?根据您更新后的回复,我假设是这样。
所以,这是最终代码。我相信我已将您提供的反馈纳入其中。如果您发现任何奇怪的地方,请随时提供更多信息。当此 运行 看起来像
时,函数中的 return
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...
代码:
#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;
}
编辑
通过 cfsetospeed
和 cfsetispeed
添加了波特率的显式设置。
另一个编辑
摆脱了多余的 tcgetattr
调用。
我正在使用 C 中的 termios
编写一个简单的程序来写入串行端口并读取 returned 数据。与串行线路上的设备的通信以回车符 return 终止。该程序很简单,目前看起来像:
#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;
}
所以:
- 端口正确打开
- 我能够写入数据(通过发送和接收数据时点亮的 LED 可以物理地看到它正在穿过线路)
- 已读 returns 有 0 个字符
如果我通过另一个设置为 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
那么,我做错了什么?这与 IO 被回车 return 终止这一事实有关吗?还是我配置不对?
编辑:所以,如果我观看字符设备 (/dev/ttyUSB0
),我实际上可以看到返回的数据 - 请参见下面的快照。所以,看起来我的问题是读取 buffer.
Or is my configuration incorrect?
是的。
问题是你的程序使用了非阻塞模式
open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY)
并将其与非规范模式相结合
s_alicat.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
即使您声明输入是终止行“with (a) carriage return”。
A“read returns with 0 characters”是可预测的,并且对于非阻塞原始读取(如您的配置)来说是正常的,只要没有可用数据.有关完整详细信息,请参阅 this answer。
要更正您的程序,请使用阻塞模式。
在获取文件描述符后插入以下语句:
fcntl(tty_fd, F_SETFL, 0); /* set blocking mode */
有关解释,请参阅 this。
关于termios配置,你的程序有一个严重的bug:它使用了termios结构s_alicat未初始化
正确的方法是使用 tcgetattr().
参考Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems
.
#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;
}
代码中的其他错误包括在数组地址足够时使用指向数组地址的指针(即地址的地址)。
write(tty_fd, &transmit, 2)
read(tty_fd, &receive, 255)
应该就是分别
write(tty_fd, transmit, 2)
read(tty_fd, receive, 255)
read() 系统调用不 return 或存储字符串,但您的程序假定它确实如此。
代码(纠正了优先错误)应该是:
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);
}
请注意,读取请求长度比要为终止空字节保留 space 的缓冲区大小小一。
附录
您的代码有一个 precedence/parentheses 错误,该错误已转移到我的代码中。违规声明是:
if ((count = read(tty_fd, &receive, 255) < 0))
对变量 count 的赋值应该是来自 read() 系统调用的 return 代码,而不是评估逻辑表达式 read() < 0
.
如果没有适当的括号,则首先执行比较,因为小于运算符的优先级高于赋值运算符。
这个错误导致 count,当有一个好的读取(即一个正的非零 return 代码)时,总是被分配值 0(即 false 的整数值).
与您的代码合并的此答案的修订代码已经过测试,并确认可以按预期运行,文本以回车符结尾 return。
@sawdust - 感谢您的帮助。我将 post 我的 工作代码和一些初步评论。
The problem is that your program uses non-blocking mode
这实际上不是问题,而且正是我想要的。我不希望读取阻塞,因为如果设备没有响应,这可能会导致程序挂起。这段代码只是为了测试我的串口是否良好。因此,使用 fcntl(tty_fd, F_SETFL, 0)
将标志设置为 0 是我不想做的。
The assignment to variable count should be the return code from the
read()
syscall
我想我是在关注你,但措辞很奇怪。是的,括号放置不当 - 感谢您指出这一点。但是,"return code" 我假设你的意思是 -1 或接收到的字节数?根据您更新后的回复,我假设是这样。
所以,这是最终代码。我相信我已将您提供的反馈纳入其中。如果您发现任何奇怪的地方,请随时提供更多信息。当此 运行 看起来像
时,函数中的 returnroot@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...
代码:
#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;
}
编辑
通过 cfsetospeed
和 cfsetispeed
添加了波特率的显式设置。
另一个编辑
摆脱了多余的 tcgetattr
调用。