从 c 中的串口读取会打断行
reading from serial port in c breaks up lines
我正在尝试用 C 编写一个小程序,它将使用 select 命令从串行端口读取数据,以便它阻塞并等待输入。它的工作,除了它不断打破线,我不知道为什么。该设备被编程为不会打断线路,并且可以与实际的终端程序一起正常工作。我以前从未在 C 中做过串行通信,而且我在 Mac 上,所以这对我来说都是新的。我真的不知道去哪里寻找问题所在。
我有一些代码可以查找并列出串口。为了简单起见,我将其省略,所以如果有一个变量没有意义,那可能就是原因。以下是打开端口、设置属性并尝试从中读取的代码,以及从 Apple 站点复制的注释(抱歉):
/* this is based on a combination of
* and https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html
*/
static int OpenSerialPort(const char *deviceFilePath, int speed)
{
int fileDescriptor = -1;
struct termios options;
memset(&options, 0, sizeof(options)); // init it
// Open the serial port read/write, with no controlling terminal,
// and don't wait for a connection.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1)
{
printf("Error opening serial port %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Note that open() follows POSIX semantics: multiple open() calls to
// the same file will succeed unless the TIOCEXCL ioctl is issued.
// This will prevent additional opens except by root-owned processes.
// See options(4) ("man 4 options") and ioctl(2) ("man 2 ioctl") for details.
if (ioctl(fileDescriptor, TIOCEXCL) == kMyErrReturn)
{
printf("Error setting TIOCEXCL on %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.
cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;
// The baud rate, word length, and handshake options can be set as follows:
cfsetspeed(&options, speed); // Set 19200 baud
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as [=10=]0 chars
options.c_iflag &= ~IGNBRK; // disable break processing
options.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
options.c_oflag = 0; // no remapping, no delays
options.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
options.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
options.c_cflag &= ~(PARENB | PARODD); // shut off parity
options.c_cflag |= false;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CRTSCTS;
// Cause the new options to take effect immediately.
if (tcsetattr(fileDescriptor, TCSANOW, &options) == kMyErrReturn)
{
printf("Error setting options attributes %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// turn on blocking
if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn)
{
printf("Error clearing O_NONBLOCK %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Success:
return fileDescriptor;
// Failure:
error:
if (fileDescriptor != kMyErrReturn)
{
close(fileDescriptor);
}
return -1;
}
int main(void)
{
int fileDescriptor;
kern_return_t kernResult; // these are Apple-specific
io_iterator_t serialPortIterator; // Apple
char deviceFilePath[MAXPATHLEN];
fd_set fdset; // make a file descriptor set
FD_ZERO (&fdset); // init it
char buf[1000]; // some strings are big
kernResult = GetDevices(&serialPortIterator);
printf("Devices on this system:\n");
kernResult = ListDevicePaths(serialPortIterator, deviceFilePath, sizeof(deviceFilePath));
IOObjectRelease(serialPortIterator); // Release the iterator.
// Open the modem port, initialize the modem, then close it.
if (!deviceFilePath[0])
{
printf("No modem port found.\n");
return EX_UNAVAILABLE;
}
fileDescriptor = OpenSerialPort("/dev/cu.usbmodem1d1111", B230400);
FD_SET (fileDescriptor, &fdset); // add to file descriptor set
// now we're going to use select to only read from the file handle when there's data available
while (1)
{
if (select (FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) // this will block the program until something is on the line
{
printf("select error\n");
}
read(fileDescriptor, buf, 1000);
printf("%s\n", buf);
memset(buf, '[=10=]', 1000);
}
// let's try to read from the serial port
/* for (int i = 0; i <= 10; i++)
{
char buf [100];
int n = read(fileDescriptor, buf, sizeof buf);
printf("%s\n", buf);
//usleep ((7 + 25) * 100);
}*/
close(fileDescriptor);
printf("Modem port closed.\n");
return EX_OK;
}
预期输出:
This is sample output.
Hello.
我在上面的程序中实际得到的是:
Thi
s is sam
ple output.
Hel
lo.
或类似的东西。每次都不一样。有时效果很好。这似乎是随机的。
所以我的问题是:
我究竟做错了什么?除了一条毯子,我还需要处理什么代码 "all of it?" 我不明白什么?我承认我并不真正理解这些库究竟是如何工作的。我假设(我知道,我知道)他们负责流量控制和错误等。但话又说回来,我从中复制的示例并没有完全解释这一点,所以我不知道。我真的不知道发生了什么。
问题是你正在读取任意数量的字节,然后用换行符分隔输出它们:
read(fileDescriptor, buf, 1000);
printf("%s\n", buf);
您打开了描述符 O_NONBLOCK
,我不确定您的 fcntl
调用是否足以清除它。结果是 read
拉出,但是在那一刻恰好缓冲了很多字符,然后打印它们后跟一个换行符。
您可能不希望 read
处于阻塞模式,因为在读取 1000 个字符之前它可能不会 return。这可能更接近你想要的:
amt = read(fileDescriptor, buf, 1000);
if (amt > 0)
write(1,buff,amt);
else
break;
当然错误处理应该多很多
it keeps breaking up lines and I have no idea why.
如果您想从串行终端读取行,则必须对其进行配置。
相反,您已将其配置为非规范和非阻塞模式。
该代码与您声明的意图完全不符。
引自 Linux termios man 页面:
In canonical mode:
Input is made available line by line. An input line is available when one
of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).
代码中清楚地注释了它使用的是非规范模式(即错误的模式):
// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.
cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;
您需要删除这些行以获得规范模式并读取行而不是原始字节。
如果您期望 read() 到 return 完整的行,那么程序将不得不等待输入。这意味着您需要阻止 I/O.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
需要从 open() 系统调用中删除 O_NONBLOCK 选项。
不管至少三位评论者写了什么,Linux 串行终端可以配置为读取行。您使用的是真正的操作系统,而不是 运行 微处理器上的裸机。您所要做的就是激活线路规则来扫描串行终端接收到的字符。
有关编程规范模式的完整详细信息,请参见 Serial Programming Guide for POSIX Operating Systems 和 termios man 页面。
您的代码还有几个问题需要更正:
- 而不是
memset(&options, 0, sizeof(options))
代码应该调用 tcgetattr() 来正确初始化结构。对于规范输入来说,这可能是一个严重的问题,因为现有代码会将所有控制代码规范归零,而不是具有正确的定义。
- 代码应该执行按位运算(以保留现有设置),而不是直接赋值。参见 Setting Terminal Modes Properly。
- 需要扩展
read(fileDescriptor, buf, 1000)
语句以处理可能的错误和处理接收到的数据。
- 来自 read() 系统调用的 return 代码需要检查是否存在任何错误情况。
- 当未检测到错误时,return 代码指示缓冲区中 return 的字节数。请注意,输入不会被空字节终止,因此在附加空字节之前不应将字符串操作应用于缓冲区。
读取的代码应该是这样的:
rc = read(fileDescriptor, buf, sizeof(buf) - 1);
if (rc < 0) {
/* handle error condition */
} else {
buf[rc] = '[=12=]';
printf("%s", buf);
}
由于buf[]分配了1000字节,read()请求最多可以return一行999 个字符长。
我正在尝试用 C 编写一个小程序,它将使用 select 命令从串行端口读取数据,以便它阻塞并等待输入。它的工作,除了它不断打破线,我不知道为什么。该设备被编程为不会打断线路,并且可以与实际的终端程序一起正常工作。我以前从未在 C 中做过串行通信,而且我在 Mac 上,所以这对我来说都是新的。我真的不知道去哪里寻找问题所在。
我有一些代码可以查找并列出串口。为了简单起见,我将其省略,所以如果有一个变量没有意义,那可能就是原因。以下是打开端口、设置属性并尝试从中读取的代码,以及从 Apple 站点复制的注释(抱歉):
/* this is based on a combination of
* and https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html
*/
static int OpenSerialPort(const char *deviceFilePath, int speed)
{
int fileDescriptor = -1;
struct termios options;
memset(&options, 0, sizeof(options)); // init it
// Open the serial port read/write, with no controlling terminal,
// and don't wait for a connection.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1)
{
printf("Error opening serial port %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Note that open() follows POSIX semantics: multiple open() calls to
// the same file will succeed unless the TIOCEXCL ioctl is issued.
// This will prevent additional opens except by root-owned processes.
// See options(4) ("man 4 options") and ioctl(2) ("man 2 ioctl") for details.
if (ioctl(fileDescriptor, TIOCEXCL) == kMyErrReturn)
{
printf("Error setting TIOCEXCL on %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.
cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;
// The baud rate, word length, and handshake options can be set as follows:
cfsetspeed(&options, speed); // Set 19200 baud
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as [=10=]0 chars
options.c_iflag &= ~IGNBRK; // disable break processing
options.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
options.c_oflag = 0; // no remapping, no delays
options.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
options.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
options.c_cflag &= ~(PARENB | PARODD); // shut off parity
options.c_cflag |= false;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CRTSCTS;
// Cause the new options to take effect immediately.
if (tcsetattr(fileDescriptor, TCSANOW, &options) == kMyErrReturn)
{
printf("Error setting options attributes %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// turn on blocking
if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn)
{
printf("Error clearing O_NONBLOCK %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Success:
return fileDescriptor;
// Failure:
error:
if (fileDescriptor != kMyErrReturn)
{
close(fileDescriptor);
}
return -1;
}
int main(void)
{
int fileDescriptor;
kern_return_t kernResult; // these are Apple-specific
io_iterator_t serialPortIterator; // Apple
char deviceFilePath[MAXPATHLEN];
fd_set fdset; // make a file descriptor set
FD_ZERO (&fdset); // init it
char buf[1000]; // some strings are big
kernResult = GetDevices(&serialPortIterator);
printf("Devices on this system:\n");
kernResult = ListDevicePaths(serialPortIterator, deviceFilePath, sizeof(deviceFilePath));
IOObjectRelease(serialPortIterator); // Release the iterator.
// Open the modem port, initialize the modem, then close it.
if (!deviceFilePath[0])
{
printf("No modem port found.\n");
return EX_UNAVAILABLE;
}
fileDescriptor = OpenSerialPort("/dev/cu.usbmodem1d1111", B230400);
FD_SET (fileDescriptor, &fdset); // add to file descriptor set
// now we're going to use select to only read from the file handle when there's data available
while (1)
{
if (select (FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) // this will block the program until something is on the line
{
printf("select error\n");
}
read(fileDescriptor, buf, 1000);
printf("%s\n", buf);
memset(buf, '[=10=]', 1000);
}
// let's try to read from the serial port
/* for (int i = 0; i <= 10; i++)
{
char buf [100];
int n = read(fileDescriptor, buf, sizeof buf);
printf("%s\n", buf);
//usleep ((7 + 25) * 100);
}*/
close(fileDescriptor);
printf("Modem port closed.\n");
return EX_OK;
}
预期输出:
This is sample output.
Hello.
我在上面的程序中实际得到的是:
Thi
s is sam
ple output.
Hel
lo.
或类似的东西。每次都不一样。有时效果很好。这似乎是随机的。
所以我的问题是: 我究竟做错了什么?除了一条毯子,我还需要处理什么代码 "all of it?" 我不明白什么?我承认我并不真正理解这些库究竟是如何工作的。我假设(我知道,我知道)他们负责流量控制和错误等。但话又说回来,我从中复制的示例并没有完全解释这一点,所以我不知道。我真的不知道发生了什么。
问题是你正在读取任意数量的字节,然后用换行符分隔输出它们:
read(fileDescriptor, buf, 1000);
printf("%s\n", buf);
您打开了描述符 O_NONBLOCK
,我不确定您的 fcntl
调用是否足以清除它。结果是 read
拉出,但是在那一刻恰好缓冲了很多字符,然后打印它们后跟一个换行符。
您可能不希望 read
处于阻塞模式,因为在读取 1000 个字符之前它可能不会 return。这可能更接近你想要的:
amt = read(fileDescriptor, buf, 1000);
if (amt > 0)
write(1,buff,amt);
else
break;
当然错误处理应该多很多
it keeps breaking up lines and I have no idea why.
如果您想从串行终端读取行,则必须对其进行配置。
相反,您已将其配置为非规范和非阻塞模式。
该代码与您声明的意图完全不符。
引自 Linux termios man 页面:
In canonical mode:
Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).
代码中清楚地注释了它使用的是非规范模式(即错误的模式):
// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.
cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;
您需要删除这些行以获得规范模式并读取行而不是原始字节。
如果您期望 read() 到 return 完整的行,那么程序将不得不等待输入。这意味着您需要阻止 I/O.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
需要从 open() 系统调用中删除 O_NONBLOCK 选项。
不管至少三位评论者写了什么,Linux 串行终端可以配置为读取行。您使用的是真正的操作系统,而不是 运行 微处理器上的裸机。您所要做的就是激活线路规则来扫描串行终端接收到的字符。
有关编程规范模式的完整详细信息,请参见 Serial Programming Guide for POSIX Operating Systems 和 termios man 页面。
您的代码还有几个问题需要更正:
- 而不是
memset(&options, 0, sizeof(options))
代码应该调用 tcgetattr() 来正确初始化结构。对于规范输入来说,这可能是一个严重的问题,因为现有代码会将所有控制代码规范归零,而不是具有正确的定义。 - 代码应该执行按位运算(以保留现有设置),而不是直接赋值。参见 Setting Terminal Modes Properly。
- 需要扩展
read(fileDescriptor, buf, 1000)
语句以处理可能的错误和处理接收到的数据。 - 来自 read() 系统调用的 return 代码需要检查是否存在任何错误情况。
- 当未检测到错误时,return 代码指示缓冲区中 return 的字节数。请注意,输入不会被空字节终止,因此在附加空字节之前不应将字符串操作应用于缓冲区。
读取的代码应该是这样的:
rc = read(fileDescriptor, buf, sizeof(buf) - 1);
if (rc < 0) {
/* handle error condition */
} else {
buf[rc] = '[=12=]';
printf("%s", buf);
}
由于buf[]分配了1000字节,read()请求最多可以return一行999 个字符长。