在C语言中二进制串口读取丢失字节

4
我正在将二进制数据从Arduino发送到运行此代码的串口。 在十六进制模式下使用Cutecom可以清晰地读取我期望在该串口上看到的内容。如下所示。
00000000: 24 04 85 ab 47 43 04 04   24 04 85 ab 47 43 04 04 
00000010: 24 04 85 ab 47 43 04 04   24 04 85 ab 47 43 04 04 

到这里都没有问题。我不认为需要提供Arduino代码。

我正在尝试用C语言读取相同的内容。然而下面的代码只会打印出这个:

24 85 ab 47 43 24 85 ab 47 43 24 85 ab 47 43

由于某些原因,它跳过了04。有什么想法吗?
#include <stdio.h>
#include <fcntl.h>   /* File Control Definitions           */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <unistd.h>  /* UNIX Standard Definitions      */ 
#include <errno.h>   /* ERROR Number Definitions           */
#include <signal.h>
#include <string.h>
#include <stdint.h>

int open_serial(char *port, int baud);

void main(void)
{
    int tty = open_serial("/dev/ttyUSB0", B115200);
    uint8_t buff[256];   /* Buffer to store the data received              */
    int  n;    /* Number of bytes read by the read() system call */

    while (1) {
        n = read(tty, &buff, sizeof buff); 
        if (n > 0){
            //printf("-%d-\n ", n);
            for(int i=0;i<n;i++){   
                printf("%02x ", buff[i]);
            }
            fflush(stdout); 
        }
    }
}

int open_serial(char *port, int baud)
{

    int fd = open( port, O_RDWR | O_NOCTTY);    

    if(fd == -1)                        /* Error Checking */
        printf("\n  Error! in Opening tty  ");

    struct termios SerialPortSettings;  /* Create the structure                          */

    tcgetattr(fd, &SerialPortSettings); /* Get the current attributes of the Serial port */

    /* Setting the Baud rate */
    cfsetispeed(&SerialPortSettings,B115200); /* Set Read  Speed as 115200                       */
    cfsetospeed(&SerialPortSettings,B115200); /* Set Write Speed as 115200                       */

    /* 8N1 Mode */
    SerialPortSettings.c_cflag &= ~PARENB;   /* Disables the Parity Enable bit(PARENB),So No Parity   */
    SerialPortSettings.c_cflag &= ~CSTOPB;   /* CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit */
    SerialPortSettings.c_cflag &= ~CSIZE;    /* Clears the mask for setting the data size             */
    SerialPortSettings.c_cflag |=  CS8;      /* Set the data bits = 8                                 */

    SerialPortSettings.c_cflag &= ~CRTSCTS;       /* No Hardware flow Control                         */
    SerialPortSettings.c_cflag |= CREAD | CLOCAL; /* Enable receiver,Ignore Modem Control lines       */ 


    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);          /* Disable XON/XOFF flow control both i/p and o/p */
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /* Non Cannonical mode                            */

    SerialPortSettings.c_oflag &= ~OPOST;/*No Output Processing*/

    /* Setting Time outs */
    SerialPortSettings.c_cc[VMIN] = 10; /* Read at least 10 characters */
    SerialPortSettings.c_cc[VTIME] = 0; /* Wait indefinetly   */


    if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) /* Set the attributes to the termios structure*/
        printf("\n  ERROR ! in Setting attributes");
    else
        printf("\n  BaudRate = 115200 StopBits = 1 Parity   = none\n");

    /*------------------------------- Read data from serial port -----------------------------*/

    tcflush(fd, TCIFLUSH);   /* Discards old data in the rx buffer            */
    return fd;
}

我怀疑这是因为0x04是传输结束(EOT)代码。 - Janne Tuukkanen
好的。这意味着在我访问它们之前,我的二进制数据已被解释。我该如何禁止这种情况发生? - Noel
04有一个特殊的含义,即“传输结束”(请参见ASCII表)。然而,大多数串行监视软件都有设置可以更改,忽略特殊字符。 - t.m.
我记得“RealTerm”有显示设置。 - t.m.
在其中有一个部分包含“// no Ctrl-D suppression”……“ONOEOT”。请注意,0x04也是Ctrl-D。 - Weather Vane
显示剩余3条评论
2个回答

5
我正在将二进制数据从Arduino发送到运行该代码的串口。
传输二进制数据需要配置POSIX串行终端为非规范(即原始)模式。
由于数值在ASCII控制字符范围内(即0x00至0x1F),因此数据一致丢失几乎总是表示termios配置不当。
由于数据丢失似乎在接收端发生,因此第一件要验证的事情是正确指定非规范输入模式。
您的代码有...
对于某些原因,它跳过了04。有任何想法吗?
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /* Non Cannonical mode                            */

赋值语句右侧的所有属性都属于 termios 结构体中的 c_lflag,而不是编码中的 c_iflag。
因此,串行终端的默认模式可能是规范模式,您的程序由于此拼写错误未能重新配置为非规范输入模式。
您通过调用 cfmakeraw() 函数来增强代码的解决方法并不理想。
使用 cfmakeraw() 函数可以方便地避免像您这样的错误。
但应该纠正有缺陷的语句,或者更好的做法是删除因 cfmakeraw() 调用而变得多余的操作。
cfmakeraw() sets the terminal to something like the "raw" mode of the old 
Version 7 terminal driver: input is available character by character, echoing is 
disabled, and all special processing of terminal input and output characters is 
disabled. The terminal attributes are set as follows:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                | INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

顺便提一下,您的代码确实使用了推荐/首选的方法来调用 tcgetattr(),然后使用布尔操作设置/修改终端属性。使用“干净结构体(clean struc)”的建议被认为是不可移植的。


补充说明

这个c_iflagICANON错误的起源似乎可以追溯到这篇来自xanthium.in的串口教程。作者在2016年就已经被通知了这个错误,但是并没有去修复它。


0

成功了,添加了

cfmakeraw(&SerialPortSettings);

就在tcsetattr函数调用上方。


1
如果你在代码中修复了错误,就不需要这行额外的代码:SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG) 应该使用 c_lflag 而不是 c_iflag - sawdust
好的。请发布您的答案。我从GitHub上某个地方捡到了那个bug。 - Noel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接