4、串口通信实现:Linux串口配置、波特率与数据位、DMA与中断模式、串口调试技巧

串口通信,说白了就是飞控和地面站之间最传统、也最可靠的那条「电话线」。PX4 通过串口把 MAVLink 数据包发出去,QGC 在另一端收回来。别看现在 USB、Wi-Fi、4G 满天飞,真正到了飞场调试、固件刷写、日志导出这些场景,串口依然是最后的保底手段。

我刚开始做飞控集成的时候,总觉得串口太简单——不就是 TX、RX、GND 三根线嘛。结果第一次联调,QGC 死活连不上飞控,折腾了整整一个下午。后来发现,是波特率配错了。嗯,从那以后我再也不敢小看串口配置了。

4.1 Linux 串口配置:从设备节点到 termios

在 Linux 下,串口设备通常映射为 /dev/ttyS0、/dev/ttyUSB0 或 /dev/ttyACM0。PX4 的串口驱动跑在 NuttX 上,但 QGC 跑在 Linux/Windows/macOS 上。我们重点讲 Linux 端的配置,因为这是开发者最常碰到的环境。

配置串口的核心,是操作 termios 结构体。我习惯把它拆成三步:

  1. 打开设备:用 open() 系统调用,注意加上 O_RDWR | O_NOCTTY | O_NDELAY。
  2. 读取当前参数:tcgetattr() 拿到当前配置。
  3. 修改并写入:设置波特率、数据位、停止位、校验位,最后 tcsetattr() 生效。

给你看一段我常用的配置代码骨架:

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

int setup_serial(const char *device, int baud) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) return -1;

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

    // 设置波特率
    cfsetispeed(&tty, baud);
    cfsetospeed(&tty, baud);

    // 8N1:8数据位,无校验,1停止位
    tty.c_cflag &= ~PARENB;   // 无校验
    tty.c_cflag &= ~CSTOPB;   // 1停止位
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;       // 8数据位

    // 启用接收,忽略调制解调器控制线
    tty.c_cflag |= CREAD | CLOCAL;

    // 原始模式输入输出
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_oflag &= ~OPOST;

    // 设置读取超时
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 10;  // 1秒超时

    tcsetattr(fd, TCSANOW, &tty);
    return fd;
}
我的习惯:把波特率定义成宏,比如 B57600、B115200。PX4 默认串口波特率是 57600,但 QGC 经常用 115200。你最好在飞控参数里统一设置 SER_GPS1_BAUD 或 MAV_0_CONFIG 对应的波特率,两边保持一致。

4.2 波特率与数据位:为什么是 57600 和 8N1?

波特率决定了串口通信的速度。PX4 和 QGC 之间最常用的波特率是 57600 和 115200。为什么不是 9600?因为 MAVLink 数据包通常几十到几百字节,9600 太慢,传心跳都卡。为什么不是 921600?因为飞控的串口硬件和线缆质量不一定撑得住,尤其是有线转接板的时候。

数据位方面,MAVLink 协议规定使用 8 位数据、无校验、1 位停止位,也就是常说的 8N1。你想想看,MAVLink 的帧结构里已经有 CRC 校验了,串口层的校验位其实是多余的。所以 8N1 是最干净的选择。

参数 PX4 默认值 QGC 常用值 说明
波特率 57600 115200 建议统一,否则连不上
数据位 8 8 MAVLink 标准
校验位 无 无 帧内 CRC 已足够
停止位 1 1 标准配置
流控制 无 无 硬件流控容易出问题
我曾经踩过的坑:有一次用 USB 转串口模块,线缆太长(超过 2 米),115200 波特率下数据频繁丢包。后来降到 57600,问题消失。所以如果你发现 QGC 断连、数据乱码,先试试降低波特率。

4.3 DMA 与中断模式:谁更适合飞控通信?

串口数据收发有两种底层模式:中断模式和 DMA 模式。PX4 的 NuttX 内核两种都支持,但默认用的是中断模式。

中断模式:每收到一个字节,CPU 就跳进中断服务程序,把数据搬到缓冲区。优点是响应快、延迟低;缺点是频繁中断会占用 CPU 时间。飞控的 CPU 本来就要跑姿态解算、控制律、传感器读取,串口中断太多会影响实时性。

DMA 模式:DMA 控制器直接搬运数据,CPU 只在缓冲区满或传输完成时才被通知。优点是 CPU 负载低,适合大数据量传输;缺点是 DMA 通道资源有限,配置复杂,而且 DMA 传输有固定长度限制。

我个人建议:MAVLink 数据量不大(通常 50~200 字节/包),用中断模式就够了。如果你在做高带宽日志下载或 OTA 固件升级,可以考虑 DMA 模式。我在一个项目中试过 DMA 传日志,速度确实快,但调试 DMA 中断优先级和缓存对齐花了不少时间。

核心对比:

  • 中断模式:简单可靠,适合小包、高频交互。PX4 默认选择。
  • DMA 模式:CPU 负载低,适合大块数据。配置门槛高,容易踩坑。

4.4 串口调试技巧:三板斧解决 90% 的问题

串口通信出问题,别慌。我总结了三个调试步骤,基本能覆盖大部分场景。

第一板斧:硬件回路测试

把 TX 和 RX 短接,用串口工具发数据,看能不能收到自己发的。收不到?检查线序、电压、驱动。收到了?说明硬件没问题,问题在软件配置。

第二板斧:波特率扫描

QGC 有个隐藏功能:自动波特率检测。但有时候它猜不准。我习惯用 stty 命令手动验证:

# 查看当前串口配置
stty -F /dev/ttyUSB0 -a

# 设置波特率并测试
stty -F /dev/ttyUSB0 57600 cs8 -cstopb -parenb
cat /dev/ttyUSB0 | xxd | head -20

如果看到一堆 0xFE 0x09 之类的 MAVLink 帧头,说明波特率对了。

第三板斧:逻辑分析仪抓波形

这是终极大法。用逻辑分析仪夹在 TX 和 GND 上,抓一段波形。看起始位、数据位、停止位是否对齐。我遇到过一种情况:飞控输出正常,但 USB 转串口模块的晶振不准,导致波特率偏差 3%,数据全错。逻辑分析仪一看就明白了。

调试小工具推荐:screen 和 minicom 是 Linux 下最轻量的串口调试工具。我常用 screen /dev/ttyUSB0 57600 直接看原始数据流。如果看到乱码,先别怀疑飞控,检查一下你的终端模拟器是不是设成了 8N1。

4.5 串口通信流程总览

下面这张图总结了从 PX4 串口驱动到 QGC 接收的完整数据流。你可以看到,数据从飞控的 MAVLink 发送线程出发,经过 NuttX 串口驱动、硬件 UART、USB 转串口模块,最后到达 QGC 的串口读取线程。每一步都可能出问题,但只要你掌握了上面的三板斧,大部分问题都能快速定位。

串口通信数据流(PX4 → QGC) PX4 MAVLink 发送线程 封装 MAVLink 帧 NuttX 串口驱动 中断/DMA 模式 硬件 UART TX / RX 引脚 USB 转串口模块 CP2102 / CH340 / FTDI Linux 串口设备 /dev/ttyUSB0 QGC 串口读取线程 解析 MAVLink 帧 ⚠️ 常见故障点 ① 波特率不匹配 → QGC 连不上飞控 ② USB 转串口驱动未安装 → 设备节点不存在 ③ 线缆过长/干扰 → 数据丢包、乱码 ④ 权限不足 → open() 返回 -1

串口通信看起来简单,但实际项目中出问题的概率不低。我见过有人因为 USB 转串口模块的驱动没装,折腾了一整天。也见过有人把 TX 和 RX 接反了,数据发出去收不回来。记住:先查硬件,再查软件,最后查配置。这三步走完,90% 的问题都能解决。