串口编程易错点笔记
基于 serial::Serial
(wjwwood serial 库)
场景:Linux / Windows 下连续发送多帧 8 字节指令,下位机只响应第一帧,第二帧“丢失”。
1. 现象
serial::Serial ser("/dev/ttyUSB0", 115200);
ser.write(opendata1, 8); // 阀门 1
ser.write(opendata2, 8); // 阀门 2
结果:阀门 2 无动作;在两包之间加 sleep(2)
后正常。
2. 根本原因
ser.write()
只把数据拷贝到操作系统 TX 缓冲区即返回。- 如果两包间隔过短,底层可能粘包;下位机解析失败,表现为“收不到”。
sleep()
只是给 MCU 赢得处理时间,并非真正同步。
3. 正确做法(使用 serial::Serial
接口)
3.1 立即刷新发送缓冲区
ser.write(opendata1, 8);
ser.flush(); // 阻塞到 TX 队列空ser.write(opendata2, 8);
ser.flush();
flush()
内部调用tcdrain(fd)
(Linux)或FlushFileBuffers(hCom)
(Windows),保证字节全部离开发送线。
3.2 可选:等待字节完全发出后再返回
serial::Serial
还提供一个更底层的阻塞接口:
ser.write(opendata1, 8);
ser.waitByteTimes(8); // 按当前波特率计算 8 字节耗时并阻塞
与
flush()
效果类似,但不依赖系统调用,可移植性更好。
4. 额外保险手段
措施 | 说明 |
---|---|
帧协议 | 每帧加头(0xAA)、长度、CRC,下位机可重新同步,防止粘包错帧。 |
ACK 机制 | 发完一帧后等待下位机回 0x06 (ACK),收到再发下一帧;彻底解决“处理不完”问题。 |
降低波特率 | 若 MCU 主频低,115200→57600 比盲目延时更稳妥。 |
5. 最小可靠模板
serial::Serial ser("/dev/ttyUSB0", 115200);
ser.setTimeout(serial::Timeout::max(), 250, 0, 250, 0); // 读超时 250 msauto send_frame = [&](const uint8_t* data, size_t len){ser.write(data, len);ser.flush(); // 确保离开发送线// 可选:等待 ACKuint8_t ack;if(ser.read(&ack, 1) == 1 && ack == 0x06) return true;return false;
};if(send_frame(opendata1, 8) && send_frame(opendata2, 8))std::cout << "两帧均成功\n";
6. 一句话总结
在
serial::Serial
里,“写完”≠“发走”;连续多帧控制务必flush()
(或waitByteTimes
),否则数据仍躺在内核缓冲区,下位机永远看不见。