4、数据读取接口设计:串口、USB、以太网接口的数据读取与驱动开发

好,咱们接着聊。黑匣子数据读出来,总得有个通道吧?

串口、USB、以太网,这三个接口,说白了就是地面回放系统的「三驾马车」。我这些年做过的项目,几乎每个都跟它们打过交道。今天我就把每个接口的驱动开发要点、踩过的坑,一股脑儿倒给你们。

4.1 串口:最老派,但也最可靠

串口,RS-232/422/485,这玩意儿年纪比我都大。但你别小看它,在航空电子设备里,串口依然是标配。为什么?简单、稳定、抗干扰。

我个人习惯,做串口驱动开发时,第一件事不是写代码,而是看硬件原理图。你得搞清楚:

  • 电平标准:是RS-232(±12V)还是RS-422(差分)?
  • 流控方式:硬件流控(RTS/CTS)还是软件流控(XON/XOFF)?
  • 波特率:黑匣子通常用115200或57600,但也有老设备用9600。
⚠️ 我曾经踩过的坑: 有一次,我直接按115200去读数据,结果全是乱码。折腾了半天,才发现那台黑匣子的波特率是38400。嗯,从那以后,我写驱动时一定会加一个「波特率自动探测」的逻辑。

串口驱动开发的核心,其实就是打开设备、配置参数、读写数据。Linux下用termios结构体,Windows下用CreateFile + DCB。我给你们看一段典型的Linux串口初始化代码:

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>

int open_serial(const char *device, int baudrate) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) {
        perror("打开串口失败");
        return -1;
    }

    struct termios options;
    tcgetattr(fd, &options);

    // 设置波特率
    cfsetispeed(&options, baudrate);
    cfsetospeed(&options, baudrate);

    // 8位数据,无校验,1位停止位
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    // 原始模式,不处理特殊字符
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    options.c_oflag &= ~OPOST;

    tcsetattr(fd, TCSANOW, &options);
    return fd;
}

这段代码,说白了就是三板斧:打开、配置、返回句柄。但有个细节你们要注意——O_NDELAY标志。我建议你们加上它,否则打开一个不存在的串口时,程序会卡死。

4.2 USB:即插即用,但驱动得小心

USB接口,现在的地面回放系统用得最多。为什么?速度快,而且支持热插拔。

但USB驱动开发,比串口复杂一个量级。你想想看,USB协议栈本身就分四层:物理层、设备层、配置层、接口层。黑匣子通常实现为CDC(通信设备类)HID(人机接口设备)

重点来了: 大部分黑匣子USB接口,其实模拟的是一个虚拟串口(VCP)。也就是说,你在应用层看到的还是串口操作,但底层走的是USB总线。这种情况下,你只需要装好驱动(比如FTDI、CP210x的驱动),然后按串口方式读写就行。

但有些黑匣子用的是批量传输(Bulk Transfer),那就得用libusb或者WinUSB来开发了。我给你们看一个用libusb读取数据的例子:

#include <libusb-1.0/libusb.h>

int read_usb_bulk(libusb_device_handle *handle, unsigned char *buffer, int length) {
    int transferred = 0;
    int ret = libusb_bulk_transfer(handle, 0x81, // 端点地址
                                   buffer, length,
                                   &transferred, 1000); // 超时1秒
    if (ret == 0) {
        printf("成功读取 %d 字节\n", transferred);
        return transferred;
    } else {
        fprintf(stderr, "USB读取失败: %s\n", libusb_error_name(ret));
        return -1;
    }
}

这里有个坑——端点地址。0x81表示IN端点(设备到主机),0x01表示OUT端点(主机到设备)。我刚开始做的时候,经常搞反,结果数据死活读不出来。后来我学乖了,先用lsusb -v把端点信息打印出来,对着看。

💡 我的小技巧: 调试USB驱动时,别一上来就写代码。先用Wireshark抓一下USB包,看看黑匣子到底在发什么数据。这样能省一半的调试时间。

4.3 以太网:快,但协议复杂

以太网接口,现在的新款黑匣子基本都标配了。速度从10M到1G都有,甚至有些军用设备用万兆网。

但以太网驱动开发,难点不在物理层,而在协议栈。黑匣子通常用UDP或TCP来传输数据。我个人建议,地面回放系统优先用UDP。为什么?

  • 实时性高:UDP没有重传机制,延迟低。
  • 简单:不需要维护连接状态。
  • 适合广播:黑匣子数据经常是多播发送的。

当然,UDP也有缺点——丢包。所以我在项目中,会在应用层加一个简单的序列号校验。每个数据包带一个递增的序号,接收端检查序号是否连续。如果发现跳号,就请求重传。

给你们看一段UDP接收的代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int setup_udp_server(int port) {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("创建socket失败");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("绑定端口失败");
        close(sock);
        return -1;
    }

    return sock;
}

int recv_udp_data(int sock, unsigned char *buffer, int max_len) {
    struct sockaddr_in from;
    socklen_t from_len = sizeof(from);
    int len = recvfrom(sock, buffer, max_len, 0,
                       (struct sockaddr *)&from, &from_len);
    if (len > 0) {
        printf("收到来自 %s:%d 的数据,长度 %d\n",
               inet_ntoa(from.sin_addr), ntohs(from.sin_port), len);
    }
    return len;
}

这段代码看起来简单,但有个细节——recvfrom的最后一个参数是0,表示阻塞模式。我建议你们在实际项目中,用selectepoll做非阻塞读取,否则主线程会被卡住。

⚠️ 我曾经踩过的坑: 有一次,我用UDP接收黑匣子数据,发现偶尔会丢包。查了半天,原来是接收缓冲区太小。Linux默认的UDP接收缓冲区只有208KB,对于高速数据流根本不够。后来我用setsockopt把缓冲区调到2MB,问题就解决了。

4.4 三种接口的对比与选择

说了这么多,你们可能会问:到底该用哪种接口?

我给你们一个参考表:

接口类型 速度 可靠性 开发难度 适用场景
串口 慢(最高115200bps) 老设备、调试接口
USB 中(最高480Mbps) 便携式地面站
以太网 快(最高1Gbps+) 中(UDP丢包) 固定式地面站、多设备组网

我个人建议:如果条件允许,优先用以太网。为什么?因为现在的地面回放系统,数据量越来越大,串口和USB根本扛不住。而且以太网可以远程访问,你坐在办公室里就能读取千里之外的黑匣子数据。

总结一下: 串口是「老黄牛」,稳定但慢;USB是「万金油」,通用但驱动麻烦;以太网是「千里马」,快但协议复杂。做驱动开发时,一定要先搞清楚黑匣子支持哪种接口,再选合适的方案。

好了,这一章就到这里。下一章,咱们聊聊数据解析——怎么把读出来的二进制数据,变成人能看懂的飞行参数。