[英]Writing to uart serial port & receiving response, losing bytes when using nonblocking mode
我为 armv7 架构制作了一个简单的 C++ 程序(使用 raspi rootfs 用 linaro gnueabihf 编译),它接受波特率、数据、串行端口等参数,并将其发送到选定的串行端口并接收响应。 至少这是它的目标。
我目前正在使用它来发送命令以通过 UART 端口禁用/启用工业屏幕上的背光。 屏幕采用以 crlf 结尾的简单文本命令并返回响应。 屏幕的规格说它使用 9600 波特,无奇偶校验,8 个数据位和 1 个停止位用于通信,非常标准。
虽然发送工作完美无缺 - 我似乎无法找到正确接收响应的方法。 我尝试以多种不同的方式配置 termios 端口结构(禁用硬件控制,使用cfmakeraw
,配置VMIN
和VTIME
值)但没有运气。
第一件事是,我正在逐字节接收所有输入(因此每个read()
调用只返回 1 个字节..),但这不是问题。
当使用没有select()
非阻塞模式时,我正在接收所有字节,但我不知道何时停止接收(我希望它是通用的,所以我发送一个命令,期待一个简单的响应,如果没有更多数据然后退出)。 自上一条消息以来,我做了一个时间计数器,所以如果在过去的 ~500 毫秒内没有收到任何消息,那么我认为不会再有任何消息了。 但这有时会丢失一些字节的响应,我不知道为什么。
使用阻塞模式时,我收到正确的字节(尽管仍然是逐字节的),但我不知道什么时候停止,最后一次调用read()
使程序挂起,因为输入中没有其他内容。
将select()
添加到阻塞调用中时,为了查看输入是否可读,我经常丢失数据(有时只接收几个字节),有时select
返回 1,但read()
阻塞,我就挂了.
当我只发送数据而不进行任何读取并使用cat -v < /dev/ttyS3
查看输入时,实际上我始终可以在串行端口上看到正确的输入,但是当我将 cat 和我的程序都作为接收器运行时,只有其中一个获取数据(或者cat
接收几个字节而我的程序接收几个字节),这表明当我尝试读取它时,某些东西正在以相同的方式“窃取”我的字节,但它可能是什么,以及为什么是这样吗?
我当前的代码(使用非阻塞读取 + 500 毫秒超时),仍然不时丢失一些字节:
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
int open_port(char* portname)
{
int fd; // file description for the serial port
fd = open(portname, O_RDWR | O_NOCTTY | O_NDELAY);
if(fd == -1) // if open is unsucessful
{
printf("Error: open_port: Unable to open %s. \n", portname);
}
else
{
//fcntl(fd, F_SETFL, 0);
fcntl(fd, F_SETFL, FNDELAY);
}
return(fd);
}
int configure_port(int fd, int baud_rate)
{
struct termios port_settings;
tcgetattr(fd, &port_settings);
cfsetispeed(&port_settings, baud_rate); // set baud rates
cfsetospeed(&port_settings, baud_rate);
cfmakeraw(&port_settings);
port_settings.c_cflag &= ~PARENB; // no parity
port_settings.c_cflag &= ~CSTOPB; // 1 stop bit
port_settings.c_cflag &= ~CSIZE;
port_settings.c_cflag |= CS8; // 8 data bits
tcsetattr(fd, TCSANOW, &port_settings); // apply the settings to the port
return(fd);
}
/**
* Convert int baud rate to actual baud rate from termios
*/
int get_baud(int baud)
{
switch (baud) {
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
case 230400:
return B230400;
case 460800:
return B460800;
case 500000:
return B500000;
case 576000:
return B576000;
case 921600:
return B921600;
case 1000000:
return B1000000;
case 1152000:
return B1152000;
case 1500000:
return B1500000;
case 2000000:
return B2000000;
case 2500000:
return B2500000;
case 3000000:
return B3000000;
case 3500000:
return B3500000;
case 4000000:
return B4000000;
default:
return -1;
}
}
unsigned char* datahex(char* string) {
if(string == NULL)
return NULL;
size_t slength = strlen(string);
if((slength % 2) != 0) // must be even
return NULL;
size_t dlength = slength / 2;
unsigned char* data = (unsigned char*)malloc(dlength);
memset(data, 0, dlength);
size_t index = 0;
while (index < slength) {
char c = string[index];
int value = 0;
if(c >= '0' && c <= '9')
value = (c - '0');
else if (c >= 'A' && c <= 'F')
value = (10 + (c - 'A'));
else if (c >= 'a' && c <= 'f')
value = (10 + (c - 'a'));
else {
free(data);
return NULL;
}
data[(index/2)] += value << (((index + 1) % 2) * 4);
index++;
}
return data;
}
int main(int argc, char **argv) {
int baud_rate = B9600;
baud_rate = get_baud(atoi(argv[1]));
if(baud_rate == -1) {
printf("Error: Cannot convert baud rate %s, using 9600\n", argv[1]);
baud_rate = B9600;
}
bool convertHex = false;
char portName[24] = "/dev/ttyS0";
bool debug = false;
bool noreply = false;
for(int i = 3; i < argc; i++) {
if(!strcmp(argv[i], "hex"))
convertHex = true;
else if(strstr(argv[i], "/dev/") != NULL)
strncpy(portName, argv[i], sizeof(portName));
else if(!strcmp(argv[i], "debug"))
debug = true;
else if(!strcmp(argv[i], "no-reply"))
noreply = true;
}
unsigned char* data = nullptr;
size_t len = 0;
if(convertHex) {
data = datahex(argv[2]);
if((int)data == (int)NULL) {
convertHex = false;
printf("Error: Couldn't convert hex value! Needs to be even length (2 chars per byte)\n");
}
else
len = strlen(argv[2])/2;
}
if(!convertHex) {
data = (unsigned char*)argv[2];
len = strlen(argv[2]);
}
int fd = open_port(portName);
if(fd == -1) {
printf("Error: Couldn't open port %s\n", portName);
if(convertHex)
free(data);
return 0;
}
configure_port(fd, baud_rate);
if(debug) {
printf("Sending data (raw): ");
for(int i =0; i< len; i++) {
printf("%02X", data[i]);
}
printf("\n");
}
size_t writelen = write(fd, data, len);
if(debug)
printf("Sent %d/%d bytes\n", writelen, len);
if(writelen != len)
printf("Error: not all bytes were sent (%d/%d)\n", writelen, len);
else if(noreply)
printf("WRITE OK");
if(!noreply) {
unsigned char ibuff[512] = {0};
int curlen = 0; // full length
clock_t begin_time = clock();
while(( float(clock() - begin_time) / CLOCKS_PER_SEC) < 0.5 && curlen < sizeof(ibuff)) {
int ret = read(fd, ibuff+curlen, sizeof(ibuff)-curlen-1);
if(ret < 0) {
ret = 1;
continue;
}
if(ret > 0) {
curlen += ret;
begin_time = clock();
}
}
if(curlen > 0) {
ibuff[curlen] = 0; // null terminator
printf("RESPONSE: %s", ibuff);
}
}
if(fd)
close(fd);
if(convertHex)
free(data);
return 0;
}
我启动程序./rs232 9600 [hex string] hex debug
屏幕应该返回类似#BLIGHT_ON!OK
的响应,但有时我会收到例如#BLI_ON!O
这可能是什么原因? 我之前使用 QtSerial <-> STM32 控制器进行了一些串行通信,并且没有出现会导致数据丢失的问题。
第一件事是,我正在逐字节接收所有输入(因此每个 read() 调用只返回 1 个字节..)[...]
这并不奇怪。 响应以 9600 波特返回,这可能比循环的一次迭代所需的每字节慢得多。 它也可能直接来自串行驱动程序的某些配置。 应该可以通过操作VMIN
和VTIME
来调整它,但请注意,这需要禁用规范模式(无论如何你可能想要这样做;见下文)。
当使用没有 select() 的非阻塞模式时,我正在接收所有字节,但我不知道何时停止接收(我希望它是通用的,所以我发送一个命令,期待一个简单的响应,如果没有更多数据然后退出)。 自上一条消息以来,我做了一个时间计数器,所以如果在过去的 ~500 毫秒内没有收到任何消息,那么我认为不会再有任何消息了。 但这有时会丢失一些字节的响应,我不知道为什么。
一切都在细节中,您没有为该案例提供这些细节。 因此,我们无法谈论您的特定数据丢失。
一般来说,如果您在没有流量控制的情况下工作,那么您必须确保在下一个字节到达之前读取每个字节,平均而言,否则很快,新字节将覆盖先前接收的字节。 VMIN
和VTIME
可以帮助解决这个问题,或者可以尝试其他方法来调整读取时间,但请注意,9600 波特响应将以超过每毫秒一个字节的速率传送字节,因此读取尝试之间的 500 毫秒延迟太长了. 假设您尝试阅读的特定响应相对较短,但这并不能解释数据丢失。
使用阻塞模式时,我收到正确的字节(尽管仍然是逐字节的),但我不知道什么时候停止,最后一次调用 read() 使程序挂起,因为输入中没有其他内容。
那么该命令需要以 CRLF 终止,但不能依赖响应同样终止? 你正在使用多么粗鲁的设备。 如果它以需要终止命令的相同方式终止其响应,那么您可能可以在规范模式下工作,并且您绝对可以观察终止符以识别传输结束。
将 select() 添加到阻塞调用中时,为了查看输入是否可读,我经常丢失数据(有时只接收几个字节),有时 select 返回 1,但 read() 阻塞,我就挂了.
在没有任何相关代码进行分析的情况下,我无法建议在这种情况下可能出现的问题,但是您确实不需要select()
。
当我只发送数据而不做任何读取,并使用 cat -v < /dev/ttyS3 查看输入时,我实际上一直可以在串行端口上看到正确的输入,
这是一个很好的测试。
然而,当我同时运行 cat 和我的程序作为接收器时,只有其中一个获取数据(或者 cat 接收一些字节而我的程序接收一些),
这正是我所期望的。 一旦程序从端口读取了一个字节,它就不再可供任何其他程序读取。 因此,如果多个程序尝试同时从同一个端口读取,那么可用数据将以某种未指定且不一定一致的方式在它们之间进行分区。
这表明当我尝试阅读它时,某些东西正在以同样的方式“窃取”我的字节,但它可能是什么,为什么会这样?
这似乎不太可能,因为当您单独运行cat
它不会以相同的方式受到影响,而且(您报告)您自己的程序的某些版本也不会受到影响。
首先,如果设备支持流量控制,那么我会启用它。 如果两者都可行,则硬件流控制优先于软件流控制。 然而,这主要是一种故障安全——如果你的程序编写得很好,我认为没有任何理由认为流控制可能会实际触发。
那么主要是除了设置串口线参数(8/n/1)之外,还应该
VMIN == 1
和VTIME == 0
读取第一个响应字节; 这允许在设备开始发送响应之前任意延迟。 或者,如果您对愿意等待设备开始发送响应的时间有一个可靠的上限,那么您可以通过在下一个中使用合适的VTIME
来跳过此步骤。 或者可能使用更大的VTIME
作为第一个字节以适应传输开始前的延迟,但如果设备未能响应,则不会挂起。VTIME == 1
(或更大)和VMIN == 0
读取剩余的响应字节。 这可能会让您在一次调用中获得整个剩余的响应,但请重复read()
直到它返回 0(或负数)。 返回 0 表示所有可用字节都已传输,并且在VTIME
十分之一秒内没有接收到新字节——即使VTIME == 1
也比 9600 波特传输中的字符间时间长得多。 请注意,您设置的VTIME
,设备发送其响应的最后一个字节与程序检测到传输结束之间的延迟VTIME
长。 您不应该在 fcntl 级别需要非阻塞模式,也不应该需要select()
。 您可能还可以应用其他 termios 设置来更好地调整串行链路另一端的特定设备的程序,但以上对于仅包含 ASCII 数据且没有控制的单命令/单响应对来说应该足够了回车符和换行符以外的字符。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.