You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux Termios串口设备跳过隔条消息问题排查

串口自定义帧发送异常问题分析与解决

问题描述

以下是操作Linux串口发送自定义帧的代码(帧起始标识0xAA、0xBB,结束标识0x77):

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h> // 补充EXIT_SUCCESS的头文件

int main()
{
    int port_fd; // 补充变量声明
    // 打开串口
    port_fd = open("/dev/ttyUSB0", O_RDWR);
    if (port_fd < 0)
    {
        printf("Error %i while opening serial port: %s\n", errno, strerror(errno));
        return EXIT_FAILURE; // 错误时退出
    }
    
    // 获取串口配置
    struct termios tty;
    if (tcgetattr(port_fd, &tty) != 0)
    {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
        close(port_fd);
        return EXIT_FAILURE;
    }

    // 修改串口配置
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag |= CS8;
    tty.c_cflag &= ~CRTSCTS;
    tty.c_cflag |= CREAD | CLOCAL;
    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO;
    tty.c_lflag &= ~ECHOE;
    tty.c_lflag &= ~ECHONL;
    tty.c_lflag &= ~ISIG;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
    tty.c_oflag &= ~OPOST;
    tty.c_oflag &= ~ONLCR;
    tty.c_cc[VTIME] = 10;
    tty.c_cc[VMIN] = 0;
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);

    // 设置串口配置
    if (tcsetattr(port_fd, TCSANOW, &tty) != 0)
    {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        close(port_fd);
        return EXIT_FAILURE;
    }

    uint8_t testMsg[] = {0xAA, 0xBB, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x77};
    write(port_fd, testMsg, sizeof(testMsg));
    
    close(port_fd); // 关闭串口
    return EXIT_SUCCESS;
}

运行时出现两个异常:

  • 调用write()时传入sizeof(testMsg)作为长度参数,串口设备无响应;改为sizeof(testMsg)+1时,设备响应正常。
  • 执行以下循环发送代码时,设备仅对隔条消息做出响应:
for(int i = 0; i < 100; i++)
{
    write(port_fd, testMsg, sizeof(testMsg));
    sleep(10);
}

串口设备配置:波特率115200、数据位8、无校验位、停止位1。

异常原因分析

核心问题出在Linux串口的输出缓冲机制:

  1. 内核默认会为串口维护输出缓冲,当写入的数据量远小于缓冲阈值(通常为4096字节)时,不会立即将数据发送到硬件,而是等待更多数据或超时触发发送。
    • 传入sizeof(testMsg)(9字节)时,数据量太小,未达到缓冲发送条件,数据滞留在内核缓冲中,设备未收到完整帧,因此无响应。
    • 传入sizeof(testMsg)+1时,额外的无效字节触发了缓冲发送逻辑,设备才收到完整帧并响应。
  2. 循环发送时,前一次写入的数据滞留在缓冲中,第二次写入后,两次数据被拼接成连续字节流发送给设备。设备的帧解析逻辑无法区分连续的起始标识,仅能正确解析其中一帧,因此隔条响应。

原代码还存在细节隐患:

  • 未声明port_fd变量类型,属于语法漏洞。
  • 未检查write()返回值,无法确认数据是否全部写入缓冲。
  • 未关闭串口,可能导致资源泄漏。

解决办法

1. 强制等待数据发送完成

在write()后调用tcdrain()函数,该函数会阻塞直到所有输出数据都发送到硬件,确保帧被完整发送:

uint8_t testMsg[] = {0xAA, 0xBB, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x77};
ssize_t bytes_written = write(port_fd, testMsg, sizeof(testMsg));
if (bytes_written != sizeof(testMsg)) {
    printf("Write error: only %zd bytes written\n", bytes_written);
}
// 等待所有数据发送到硬件
tcdrain(port_fd);

2. 禁用串口输出缓冲(可选)

通过修改termios配置,禁用输出缓冲,让数据立即发送:

// 在修改串口配置时添加以下代码
tty.c_oflag |= OPOST;
tty.c_oflag &= ~ONLCR;
// 设置最小发送字节数为1
tty.c_cc[VEOF] = 1;

注意:此方法可能降低串口发送效率,适合小帧频繁发送的场景。

3. 完善错误处理

  • 每次系统调用(open、tcgetattr、tcsetattr、write)后都要检查返回值,出现错误时及时关闭串口并退出。
  • 程序结束前必须调用close(port_fd)释放串口资源。

优化后的循环发送代码

for(int i = 0; i < 100; i++)
{
    ssize_t bytes_written = write(port_fd, testMsg, sizeof(testMsg));
    if (bytes_written != sizeof(testMsg)) {
        printf("Loop %d write failed: %zd bytes written\n", i, bytes_written);
        break;
    }
    tcdrain(port_fd); // 确保当前帧发送完成
    sleep(10);
}

内容的提问来源于stack exchange,提问作者user20278411

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 04:28:15