windows下应使用createfile+setcommstate,linux下用open+tcsetattr;windows须用"\.comx"格式,linux需加入dialout组并确认设备路径;设参时须同步配置波特率、数据位、停止位、校验位及流控超时,避免仅设波特率导致丢帧。

串口通信该用哪个库:Windows下优先选 WinAPI,Linux下直接用 termios
跨平台库(比如 libserial 或 Boost.ASIO)看着省事,但实际项目里容易卡在权限、依赖或版本兼容上。刚起步调试时,不如直连系统 API:Windows 用 CreateFile + SetCommState,Linux 用 open + tcsetattr。这样出错能一眼看到是权限问题还是参数设错了。
常见错误现象:open("/dev/ttyUSB0", O_RDWR) 返回 -1 且 errno == EACCES → 用户没加 dialout 组;CreateFile 返回 INVALID_HANDLE_VALUE → 串口号写成 "COM1" 却漏了 \\.\ 前缀(正确是 \\.\COM1)。
- Windows 下必须用
\\.\COMx格式访问 COM 口,否则CreateFile直接失败 - Linux 下设备路径一般是
/dev/ttyS0(板载)、/dev/ttyUSB0(USB转串口),用ls /dev/tty*确认 - 波特率、数据位等参数在 Windows 和 Linux 设置方式不同,别照搬代码
SetCommState 和 tcsetattr 怎么配才不丢数据
最常踩的坑是只设波特率,忽略流控和超时。串口不是“发完就完”,接收缓冲区溢出、发送阻塞、无应答等待都会导致丢包或卡死。
Windows 示例关键点:
DCB dcb = {0};
dcb.DCBlength = sizeof(dcb);
GetCommState(hSerial, &dcb);
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
dcb.fRtsControl = RTS_CONTROL_DISABLE; // 关流控,除非设备真要
dcb.ReadIntervalTimeout = MAXDWORD; // 允许读取任意长度,不按字节等
dcb.ReadTotalTimeoutConstant = 1000; // 整个 read 最多等 1s
Linux 示例关键点:
struct termios tty; cfmakeraw(&tty); // 清掉所有输入处理(如回车转换、信号字符) tty.c_cflag &= ~CRTSCTS; // 关硬件流控 tty.c_cflag |= CREAD | CLOCAL; // 允许读、忽略 modem 控制线 tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; // 8 数据位 tty.c_cflag &= ~PARENB; // 无校验 tty.c_cflag &= ~CSTOPB; // 1 停止位 tty.c_cc[VMIN] = 0; // 非阻塞读,有数据就读,没数据立刻返回 tty.c_cc[VTIME] = 10; // 最多等 1 秒(单位 0.1s)
-
VMIN=0+VTIME>0是最稳妥的读模式:有数据立刻返回,没数据最多等 VTIME×0.1 秒 - Windows 的
ReadIntervalTimeout=MAXDWORD表示“收到第一个字节后,后续字节无限等待”,适合帧头明确的协议 - 千万别开
IXON/IXOFF(软件流控),除非你确认设备支持 XON/XOFF 字符
读写操作怎么避免阻塞或丢帧
串口读写本质是 I/O,直接调 ReadFile 或 read() 容易卡住或截断。真实场景中,数据是分片到达的,一帧可能跨两次 read,也可能一次 read 拿到两帧。
- Windows 下用
WaitForSingleObject+PeekNamedPipe判断是否有数据可读,再调ReadFile,别裸写ReadFile死等 - Linux 下建议用
select()或poll()监听 fd,避免read()阻塞主线程 - 无论哪边,都要做缓冲区拼接:把每次读到的字节存入一个
std::vector<uint8_t></uint8_t>,然后按协议(比如帧头0xAA+ 长度字段)切帧 - 写操作同样要注意:大包分次
WriteFile或write(),检查返回值是否等于预期长度,不等就得重试或报错
为什么程序一运行就提示“拒绝访问”或“Permission denied”
这不是代码问题,是权限问题。Windows 下某些 COM 口被系统服务(如蓝牙、Modem)占用;Linux 下普通用户默认没权限访问 /dev/tty* 设备。
- Windows:用设备管理器确认 COM 口是否被其他程序独占;也可尝试以管理员身份运行程序
- Linux:执行
sudo usermod -a -G dialout $USER,然后**完全退出当前会话重新登录**(仅newgrp不生效) - MacOS:设备路径是
/dev/cu.usbserial-xxx,需加uucp组,命令是sudo dseditgroup -o edit -a $USER -t user uucp - 虚拟机里串口可能根本没透传,先在宿主机
dmesg | grep tty确认设备存在
真正麻烦的从来不是打开串口,而是确定数据什么时候算“一帧完整”,以及设备断连后如何静默恢复。这些得靠协议层设计,底层 API 只管通不通。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











