gpt4 book ai didi

c++ - 写入uart串行端口并接收响应,使用非阻塞模式时会丢失字节

转载 作者:行者123 更新时间:2023-11-30 16:09:34 25 4
gpt4 key购买 nike

我为armv7体系结构制作了一个简单的c ++程序(使用raspi rootfs与linaro gnueabihf编译),该程序接受具有波特率,数据,串行端口等的参数,并将其发送到选定的串行端口并接收响应。至少那是它的目标。

我目前正在使用它来发送命令,以通过UART端口禁用/启用工业屏幕上的背光。屏幕将接受一个以crlf结尾的简单文本命令,并返回响应。屏幕的规格说明它使用9600波特,无奇偶校验,8个数据位和1个停止位进行通讯,因此非常标准。

尽管发送过程完美无瑕-我似乎找不到能够正确接收响应的方法。我尝试以多种不同方式配置termios端口结构(禁用硬件控制,使用cfmakeraw,配置VMINVTIME值),但是没有运气。

第一件事是,我按字节接收所有输入(因此,每个read()调用都恰好返回1个字节..),但这不是问题。

当使用不带select()的非阻塞模式时,我正在接收所有字节,但是我不知道何时停止接收(并且我希望它是通用的,因此我发送命令,期望得到简单的响应,如果没有更多响应数据,然后退出)。自上一条消息以来,我做了一个时间计数器,因此,如果在过去约500毫秒内什么都没收到,那么我认为不会再有其他消息了。但这有时会丢失一些字节的响应,我不知道为什么。

使用阻塞模式时,我接收到正确的字节(虽然仍然是逐字节),但是我不知道何时停止,对read()的最后一次调用使程序挂起,因为输入中没有其他内容。

select()添加到阻塞调用中时,要查看输入是否可读,我会遇到非常频繁的数据丢失(有时仅接收一些字节)的情况,有时select返回1,但read()会阻塞,左挂。

当我不做任何读取就发送数据,并使用cat -v < /dev/ttyS3查看输入时,实际上我一直都能在串行端口上看到正确的输入,但是当我同时将cat和程序作为接收器运行时,它们中只有一个数据(或cat接收一些字节,而我的程序接收一些),这表明我有一些东西在尝试读取时以相同的方式“窃取”我的字节,但可能是什么,为什么呢?那?

我当前的代码(使用无阻塞读取+ 500ms超时),仍然会不时丢失一些字节:

#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之类的程序

scren应该返回类似 #BLIGHT_ON!OK的响应,但有时我会收到例如 #BLI_ON!O

这可能是什么原因?我早些时候使用QtSerial <-> STM32控制器进行了一些串行通讯,但没有这样的问题会导致数据丢失。

最佳答案

首先是,我逐字节接收所有输入(因此每个输入
  read()调用恰好返回1个字节。)[...]


这不足为奇。响应以9600波特的速度返回,这可能比循环所需的一次迭代慢得多。它也将直接由串行驱动程序的某些配置引起。应该可以通过操作VMINVTIME来对此进行调整,但是请注意,这需要禁用规范模式(无论如何,您可能都想这样做;请参见下文)。


  当使用不带select()的非阻塞模式时,我将接收所有字节,
  但我不知道什么时候停止接收(我希望它成为
  通用,所以我发送命令,希望得到简单的响应,如果有
  没有更多数据,然后退出)。自上次以来我做了一个时间计数器
  消息,所以如果在过去〜500ms内什么都没收到,那么我认为
  没有其他东西了。但这有时会丢失一些字节
  反应,我不知道为什么。


这一切都在细节中,您没有针对该案例进行介绍。因此,我们无法谈谈您的特定数据丢失。

一般而言,如果没有流控制,那么必须确保在下一个字节到达之前平均读取每个字节,否则很快,新字节将覆盖以前接收的字节。 VMINVTIME可以帮助解决此问题,或者可以尝试其他方法来调整读取定时,但是请注意,9600波特响应将以超过每毫秒1个的速率传送字节,因此两次读取之间的延迟为500毫秒太长了。假设您尝试读取的特定响应相对较短,但是,这不能解释数据丢失的原因。


  使用阻止模式时,我收到正确的字节(仍然逐字节
  虽然),但我不知道何时停止以及对read()的最后一次调用
  使程序挂起,因为输入中没有其他内容。


因此要求该命令以CRLF终止,但是不能依靠响应来同样终止吗?您正在使用多么粗鲁的设备。如果它以与终止命令相同的方式终止其响应,则您可能可以在规范模式下工作,并且您肯定会注意终止符是否能够识别传输结束。


  将select()添加到阻塞调用时,查看输入是否为
  可读性强,我经常丢失数据(有时只是收到一个
  几个字节),有时select返回1,但read()块使我
  左挂。


在没有任何相关代码可供分析的情况下,我无法提出问题所在,但是您实际上不需要select()


  当我不做任何阅读就发送数据并查看输入时
  使用cat -v   一直都有串口


这是一个很好的测试。


  但是,当我同时运行cat和我的程序时
  作为接收者,只有其中一个接收数据(或者猫接收到一些
  字节和我的程序一些),


这正是我所期望的。一旦程序从端口读取了一个字节,其他任何程序都将无法再读取该字节。因此,如果多个程序试图同时读取同一端口,则可用数据将以某种未指定且不一定一致的方式在它们之间进行分区。


  这表明我有些事情是
  尝试读取字节时以相同的方式“窃取”我的字节,但是可以
  是的,为什么会这样?


考虑到cat单独运行时不会受到相同的影响,(报告)您自己程序的某些版本,这似乎不太可能。



首先,如果设备支持流控制,那么我将启用它。如果两者都可行,则硬件流控制优先于软件流控制。但是,这主要是故障保护功能-我看不出任何理由认为,如果程序编写得当,流控制实际上可能会触发。

然后,除了设置串行线路参数(8 / n / 1)外,您还应该


禁用规范模式。这是必需的,因为(显然)您(显然)不能依赖于由行终止符终止的响应。
禁用回声。
避免在文件上启用非阻止模式。
(可选)使用VMIN == 1VTIME == 0读取第一个响应字节;这允许设备开始发送响应之前的任意延迟。另外,如果您愿意等待设备开始发送响应的时间有一个可靠的上限,则可以在下一个中使用合适的VTIME来跳过此步骤。或者对于第一个字节使用较大的VTIME来适应传输开始之前的延迟,但是如果设备无法响应,则不会挂起。
请使用VTIME == 1(或更大)和VMIN == 0读取其余的响应字节。这可能会在一个调用中让您获得整个响应的其余部分,但是请重复read()直到返回0(或负数)。返回值0表示所有可用字节均已传输,并且在VTIME的十分之一秒内没有收到新的字节,这比9600波特传输的字符间时间还要长得多,即使对于VTIME == 1。请注意,VTIME越大,设备发送其响应的最后一个字节与检测传输结束的程序之间的延迟就越长。
在连续的读取尝试之间不要实施任何人为的延迟。


您不需要在fcntl级别使用非阻塞模式,也不需要select()。您可能还可以使用其他termios设置来更好地调整串行链接另一端的特定设备的程序,但是以上内容对于仅具有ASCII数据且无控制权的单命令/单响应对就足够了除回车符和换行符外的其他字符。

关于c++ - 写入uart串行端口并接收响应,使用非阻塞模式时会丢失字节,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59106619/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com