如何在Linux中通过串行方式发送原始二进制数据,而无需使用Linux中的非本地库

问题描述

我目前正在尝试通过串行方式将十进制格式的原始二进制数据发送到外部设备。我目前将数据保存在缓冲区数组中,但希望使用这样的结构:

struct packetData{
   
    uint8_t sync1;
    uint8_t sync2;
    uint16_t messageId;
    uint16_t dataWordCount;
    uint16_t flags;
    uint16_t checksum;
};

我还使用9600波特,并且使用cfmakeraw设置了所有termios设置,而我目前正在使用:

  #include <stdio.h> 
 #include <stdint.h>
 #include <unistd.h> 
 #include <fcntl.h>   
 #include <termios.h> 
 #include <string.h>  
 #include <errno.h>   
 #include <stdlib.h>   
int flags = O_RDWR | O_NOCTTY | O_NDELAY;
    fd = open(device,flags);
    uint16_t buf_tx[BUFFER_SIZE] = {255,129,191,2057,0};
    if(fd == -1){
        printf("\n Failed to open port! ");
        return -1;
    }
    
    tcgetattr(fd,&tty);      //Get the current attributes of the Serial port 
    cfmakeraw(&tty);
    cfsetispeed(&tty,B9600); //Set read speed as 9600 baud                
    cfsetospeed(&tty,B9600); //Set write speed as 9600 baud              
    if((tcsetattr(fd,TCSANow,&tty)) != 0){
         printf("Error! Can't set attributes.\n");
         return -1;
    }
     
    else{
         printf("Connection successful! \n");
    }
while(x < 1000){
memset(buf_tx,sizeof(buf_tx));
        
        tcflush(fd,TCOFLUSH);
        if(y < 5){
            if(write(fd,buf_tx,5) == -1){
                printf("\n");
                printf("Error>>: %s\n",strerror(errno));
                y++;
            }
        }
        tcflush(fd,TCIOFLUSH);
        usleep(1000); 
        x++;  
}

代码不是完整的代码,仅是设置/写入部分,因此无需担心其语法。如果可能的话,最好不要有该缓冲区数组,而直接使用该结构,但是我会尽力而为。

解决方法

似乎您的串行端口或多或少都在手。我更喜欢自己明确设置termios成员组件,但是cfmakeraw()也很好。

您应该考虑的是,具有一个单独的功能来一次发送一个或多个这些结构。例如,

int write_all(const int fd,const void *buf,const size_t len)
{
    const char *data = buf;
    size_t      written = 0;
    ssize_t     n;

    while (written < len) {
        n = write(fd,data + written,len - written);
        if (n > 0) {
            written += n;
        } else
        if (n != -1) {
            /* C library bug,should never occur */
            errno = EIO;
            return -1;
        } else {
            /* Error; n == -1,so errno is already set. */
            return -1;
        }
    }

    /* Success. */
    return 0;
}

如果所有数据均已成功写入,该函数将返回0,如果发生错误,则返回-1,并设置errno

要发送struct packetData pkt;,只需使用write_all(fd,&pkt,sizeof pkt)
要发送完整的数组struct packetData pkts[5];,请使用write_all(fd,pkts,sizeof pkts)。 要从n开始发送pkts[i]数据包,请使用write_all(fd,pkts + i,n * sizeof pkts[0])

但是,您不想使用tcflush() 。它没有按照您的想象做。它实际上只是丢弃数据。

相反,要确保已传输您写入的数据,您需要使用tcdrain(fd)

我建议不要在tcdrain(fd)函数的末尾添加write_all(),因为它会阻塞,暂停程序,直到发送完数据为止。这意味着您仅应在进行需要另一端接收到传输的操作之前使用tcdrain();例如在尝试读取响应之前。

但是,如果这是一个查询响应界面,并且您确实打算从串行设备中读取,则应设置tty.c_cc[VMIN]tty.c_cc[VTIME]以反映您打算如何使用该界面。我更喜欢异步全双工操作,但这需要select() / poll()处理。对于半双工,仅具有这些确切的结构,您可以将tty.c_cc[VMIN] = sizeof (struct packetData)与说tty.c_cc[VTIME] = 30一起使用,这会导致read()尝试等待直到完整结构可用,但最多30毫秒(3.0秒)。 tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 1;之类的东西更常见;如果在十分之一秒(0.1秒)内未收到其他数据,则read()会返回短计数(甚至为0!)。然后,接收函数可能如下所示:

int read_all(const int fd,void *buf,const size_t len)
{
    char *const ptr = buf;
    size_t      have = 0;
    ssize_t     n;

    /* This function is to be used with half-duplex query-response protocol,so make sure we have transmitted everything before trying to
       receive a response. Also assumes c_cc[VTIME] is properly set for
       both the first byte of the response,and interbyte response interval
       in deciseconds. */
    tcdrain(fd);

    while (have < len) {
        n = read(fd,ptr + have,len - have);
        if (n > 0) {
            have += n;
        } else
        if (n == 0) {
            /* Timeout or disconnect */
            errno = ETIMEDOUT;
            return -1;
        } else
        if (n != -1) {
            /* C library bug,should never occur */
            errno = EIO;
            return -1;
        } else {
            /* Read error; errno set by read(). */
            return -1;
        }
    }
    /* Success; no errors. */
    return 0;
}

如果返回-1errno == ETIMEDOUT,则对方花费了很长时间才能回答。缓冲区中可能有剩余的较晚响应,您可以使用tcflush(TCIFLUSH)(或使用tcflush(TCIOFLUSH)丢弃,也可以丢弃所有尚未发送的写入数据)。在这种情况下,同步有点困难,因为上面的read_all()函数不会返回接收到的字节数(因此也将丢弃部分结构的字节数)。

有时使用的接口总是返回字节数,但也会设置errno(如果未发生错误则设置为0,否则设置为非零error constant)。对于查询响应接口的读写功能来说,这样做会更好,但是许多程序员发现此用例为“奇数”,即使按POSIX.1标准(此处是相关标准)完全可以。