我正在编写一些软件来处理Beaglebone系统的串行端口读/写。操作系统是Debian9。我正在用--std=gnu99
用C编写代码。
这是我的代码:
// reference
// https://www.cmrr.umn.edu/~strupp/serial.html
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
int open_port(void)
{
int fd;
fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY);
// removing O_NDELAY no difference
if(fd == -1)
{
perror("open_port: Unable to open /dev/ttyS1 - ");
}
else
{
fcntl(fd, F_SETFL, 0);
}
return fd;
}
int main()
{
// open fd for serial port
int fd = open_port();
// set baud rate
struct termios options;
// get current options
tcgetattr(fd, &options);
// set input and output baud
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
// enable reciever and set local mode
// CLOCAL: ignore modem control lines
// CREAD: enable reciever
options.c_cflag |= (CLOCAL | CREAD);
// set partity bit
//options.c_cflag &= PARENB;
options.c_cflag &= ~PARENB;
// use even parity
//options.c_cflag &= ~PARODD;
// use only 1 stop bit
options.c_cflag &= ~CSTOPB;
// set character size to 8 bit
options.c_cflag &= ~CSIZE;
options.c_cflag &= CS8;
// disable flow control
//options.c_cflag &= ~CNEW_RTSCTS; // does not work?
// note: check local options, some may be required
// disable canonical input (use raw)
// disable echoing of characters
// disable echoing of erase characters
// disable signals SIGINTR SIGSUSP SIGDSUSP, SIGQUIT
// input characters are passed through exactly as recieved
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// disable parity check
options.c_iflag &= IGNPAR;
// disable flow control (software)
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// set raw output, no output processing
options.c_oflag &= ~OPOST;
// set options
tcsetattr(fd, TCSANOW, &options);
char data[] = {0x01, 0x03, 0x00, 0x00};
int n = write(fd, data, 4);
if(n =! 4)
{
printf("write fail %d\n", n);
}
char buffer[40];
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
char data2[] = {0x00, 0x00, 0x00, 0x01};
n = write(fd, data2, 4);
if(n != 4)
{
printf("write fail %d\n", n);
}
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
printf("process complete\n");
// to close
close(fd);
}
我遇到的问题是致电read()
请勿阻止。这不是期望的行为。这些调用应阻塞,直到接收到2个字节的数据为止。
我的猜测是我在某个地方配置了错误的选项。但是我不知道错误在哪里,根据我的研究,这应该是在阻塞模式下阅读。 (fcntl(fd, F_SETFL, 0);
)
答案 0 :(得分:1)
我遇到的问题是致电
read()
请勿阻止。
这实际上是结论,而不是观察结果。
大概您的程序报告读取的字节数为零,从技术上讲这不是错误。
...根据我的研究,这应该是在阻止模式下阅读。 (
fcntl(fd, F_SETFL, 0);
)
正确,您已将文件描述符配置为阻止模式。
我的猜测是我在某个地方配置了错误的选项。
是的,您的termios配置不完整。 (它使用正确的按位运算,但不幸的是它不完整并且有错误,请参阅附录。)
在配置为非规范模式时,还必须指定 VMIN 和 VTIME 参数。
如果未指定(如您的代码中所示),则默认值 VMIN = 0和 VTIME = 0可能有效,这等效于非阻塞读。
请注意, read()系统调用中的字节长度对完成行为(除了限制要返回的字节数上限)影响很小。
在 man 页面上进行 read(2):
read()尝试从文件描述符
count
读取多达fd
个字节到从buf
开始的缓冲区中。
因此,count
参数实际上是最大值。
读取的实际字节数可以在零和count
之间,具体取决于为 VMIN 和 VTIME 配置的值(以及可用的实际数据)。
有关更多详细信息,请参见Linux Blocking vs. non Blocking Serial Read。
要在每个系统调用中读取至少两个字节(没有时间限制),请在termios配置中包括以下内容:
options.c_cc[VMIN] = 2;
options.c_cc[VTIME] = 0;
在系统调用 tcsetattr()之前插入到程序中的上述两个语句将导致您的程序永远阻塞,直到每个read(fd, buffer, 2)
语句中可以恰好返回两个字节为止。
read(fd, buffer, 4)
语句也将永远阻塞,并返回2、3或4个字节的数据(取决于与数据接收和缓冲有关的程序时序)。
附录
发现termios初始化中存在一些错误。
发布后,您的程序无法正确发送或接收任何数据,并且您未能检测到这些错误或忽略了提及它们。
程序中的以下语句消除了之前的所有c_cflag
操作,并且无意中将波特率重新配置为 B0 ,并将字符大小重新配置为 CS5 :>
options.c_cflag &= CS8;
正确的语法是:
options.c_cflag |= CS8;
声明
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
应使用更多属性进行扩展,以匹配 cfmakeraw():
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
程序中的以下语句会禁用c_iflag
中除 IGNPAR 之外的所有属性,并使 IGNPAR 属性处于未知/模糊状态:
// disable parity check
options.c_iflag &= IGNPAR;
要匹配 cfmakeraw(),应将其更改为:
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL);
在提到了所有更正后,我可以使您的程序按预期执行。