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

Jetson Nx(Ubuntu)下C语言UART通信丢失首字节问题求助

问题根源与解决方案

核心问题分析

你的C程序丢首字节的主要原因有两个:

  1. 串口打开后过早执行缓冲区清空:在open串口后立刻调用tcflush,此时如果PC已经开始发送数据(比如首字节EB已经进入串口缓冲区),这会直接将其清空,导致后续接收缺失。
  2. 读取后立即清空缓冲区:while循环中每次read后都执行tcflush,这会丢弃串口缓冲区中未被读取的剩余字节。因为PC发送的是29字节的完整数据包,而VMIN=1的配置下,read可能只返回当前可用的部分字节(比如1个或几个),剩余字节会被tcflush直接丢掉,下次读取时就会出现首字节丢失的情况。

另外你提到Python程序正常,是因为Python的串口库默认会处理缓冲区数据的累积,不会随意丢弃未读取的字节。

修复后的代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>

int TestUart(const char * portName, int nSpeed, int nBits, char nEvent, int nStop);

int main(){
    TestUart("/dev/ttyTHS0",9600,8,'N',1);
    return 0;
}


int TestUart(const char * portName, int nSpeed, int nBits, char nEvent, int nStop)
{
    int Port = open(portName, O_RDWR | O_NOCTTY | O_NDELAY);
    if (Port == -1)
    {
        printf("UART.cpp : Unable to open port.\n");
        return -1;
    }
    else
    {
        printf("UART.cpp : Port opened. Setting Port options...\n");
    }

    // 恢复阻塞模式
    fcntl(Port, F_SETFL, 0);

    struct termios newtio, oldtio;
    if (tcgetattr(Port, &oldtio) != 0)
    {
        perror("SetupSerial 1");
        close(Port);
        return -1;
    }

    // 基于原有配置修改,避免遗漏默认参数
    newtio = oldtio;

    // 配置串口核心参数
    newtio.c_cflag |= CLOCAL | CREAD;
    newtio.c_cflag &= ~CSIZE;

    switch (nBits)
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }

    switch (nEvent)
    {
    case 'O':
    case 'o':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        break;
    case 'E':
    case 'e':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
    case 'N':
    case 'n':
        newtio.c_cflag &= ~PARENB;
        break;
    }

    // 配置波特率
    cfsetispeed(&newtio, B9600);
    cfsetospeed(&newtio, B9600);

    // 禁用硬件流控,禁用软件流控
    newtio.c_cflag &= ~CRTSCTS;
    newtio.c_iflag &= ~(IXON | IXOFF | IXANY);

    // 停止位配置
    if (nStop == 1)
    {
        newtio.c_cflag &= ~CSTOPB;
    }
    else if (nStop == 2)
    {
        newtio.c_cflag |= CSTOPB;
    }

    // 配置为原始模式,禁用规范输入、回显等
    newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    newtio.c_oflag &= ~OPOST;

    // 关键配置:等待29字节(完整数据包长度),超时500ms
    newtio.c_cc[VTIME] = 5;  // 单位:0.1秒,5=500ms
    newtio.c_cc[VMIN] = 29;   // 等待直到收到29字节

    if ((tcsetattr(Port, TCSANOW, &newtio)) != 0)
    {
        perror("com set error");
        close(Port);
        return -1;
    }

    // 配置完成后清空缓冲区,确保之前的残留数据被清除
    tcflush(Port, TCIFLUSH);

    int num = 0;
    unsigned char rx_data[256];

    while(1)
    {
        // 此时read会阻塞直到收到29字节或超时
        int m_curPacket_length = read(Port, rx_data, sizeof(rx_data));
        if (m_curPacket_length < 0) {
            perror("read error");
            continue;
        }

        printf("    Data %d [port = %s,length = %d]: ",++num,portName,m_curPacket_length);
        for (int i = 0; i < m_curPacket_length; i++) {
            printf("%02X ", rx_data[i]);
        }
        printf("\n");
    }

    close(Port);
    return 0;
}

关键修改点说明

  1. 调整open参数:添加O_NDELAY以非阻塞方式打开,之后再用fcntl恢复阻塞,避免open时阻塞等待串口数据。
  2. 基于原有配置修改:用newtio = oldtio替代bzero,保留系统默认的串口参数,避免因清空结构体导致的隐藏配置问题。
  3. 移除错误的flush操作:删除open后的tcflush和usleep,仅在配置完串口参数后执行一次tcflush,清除残留数据。
  4. 正确配置VMIN和VTIME:设置VMIN=29(匹配PC发送的数据包长度),VTIME=5(超时500ms),确保read一次性读取完整数据包,避免分块读取导致的字节丢失。
  5. 移除while循环中的tcflush:不再丢弃未读取的缓冲区数据,确保所有发送的字节都能被完整读取。
  6. 使用unsigned char存储接收数据:避免char符号扩展导致的打印异常(比如0xEB作为char会被解析为负数,打印时显示FFFFFFEB)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 13:20:48