Jetson Nx(Ubuntu)下C语言UART通信丢失首字节问题求助
问题根源与解决方案
核心问题分析
你的C程序丢首字节的主要原因有两个:
- 串口打开后过早执行缓冲区清空:在
open串口后立刻调用tcflush,此时如果PC已经开始发送数据(比如首字节EB已经进入串口缓冲区),这会直接将其清空,导致后续接收缺失。 - 读取后立即清空缓冲区:
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; }
关键修改点说明
- 调整open参数:添加
O_NDELAY以非阻塞方式打开,之后再用fcntl恢复阻塞,避免open时阻塞等待串口数据。 - 基于原有配置修改:用
newtio = oldtio替代bzero,保留系统默认的串口参数,避免因清空结构体导致的隐藏配置问题。 - 移除错误的flush操作:删除
open后的tcflush和usleep,仅在配置完串口参数后执行一次tcflush,清除残留数据。 - 正确配置VMIN和VTIME:设置
VMIN=29(匹配PC发送的数据包长度),VTIME=5(超时500ms),确保read一次性读取完整数据包,避免分块读取导致的字节丢失。 - 移除while循环中的tcflush:不再丢弃未读取的缓冲区数据,确保所有发送的字节都能被完整读取。
- 使用unsigned char存储接收数据:避免char符号扩展导致的打印异常(比如0xEB作为char会被解析为负数,打印时显示FFFFFFEB)。
内容的提问来源于stack exchange,提问作者kevinshift
相关产品推荐
相关产品推荐

