C++串口读取函数致程序停顿、数据堆积的原因及解决方法
串口读取重复输出+程序冻结问题排查与修复
问题描述
我用Arduino持续发送字符串<123456789ABC>,但独立的C++串口读取程序会一次性批量输出数十次该字符串,之后冻结wait_time时长,循环往复。
读取串口数据的函数
std::string SerialPort::readSerialPort(int wait_time) { DWORD bytes_read; char inc_msg[1]; std::string complete_inc_msg; bool began = false; // Timer unsigned long start_time = time(nullptr); ClearCommError(this->handler, &this->errors, &this->status); // after wait_time elapsed while ((time(nullptr) - start_time) < wait_time) { if (this->status.cbInQue > 0){ if (ReadFile(this->handler, inc_msg, 1, &bytes_read, NULL)) { // < and > delimiters if (inc_msg[0] == '<' || began) { began = true; if (inc_msg[0] == '>') { return complete_inc_msg; } if (inc_msg[0] != '<') complete_inc_msg.append(inc_msg, 1); } } else return "Warning: Failed to receive data.\n"; } } return complete_inc_msg; }
主程序调用方式
std::string readResult = gyroport->readSerialPort(1); cout << readResult << "\n";
gyroport是包含所有串口通信函数(init、read、write等)的串口类对象。
控制台输出
123456789ABC 123456789ABC 123456789ABC ...(重复数十次) 123456789ABC 123456789ABC ...(重复数十次)
每段批量输出间隔期间,程序会冻结wait_time时长,之后瞬间输出一批消息,再次冻结。
问题原因
- 串口状态未实时更新:
ClearCommError仅在函数开头执行一次,后续循环中status.cbInQue的值不会刷新,导致串口缓冲区新到的数据无法被检测,直到下次调用函数才会重新查询。 - 阻塞式循环等待:函数内的while循环会持续占用CPU,直到
wait_time超时,期间程序完全冻结,无法处理其他逻辑。 - 重复读取历史数据:每次调用
readSerialPort时,只要缓冲区有数据(包括上次未处理完的),就会从头解析,导致同一个字符串被反复读取输出。
修复方案
修改思路
- 实时更新串口缓冲区状态:每次循环都调用
ClearCommError刷新cbInQue的值。 - 避免阻塞等待:改为非阻塞方式处理当前缓冲区已有数据,解析完完整帧后立即返回,不等到超时。
- 保持帧解析状态:跨函数调用跟踪帧的起始/结束,避免重复解析同一帧数据。
修改后的读取函数
std::string SerialPort::readSerialPort() { DWORD bytes_read; char inc_msg[1]; std::string complete_inc_msg; static bool began = false; // 用static保持状态,跨调用持续跟踪帧起始 // 每次循环都刷新串口状态 ClearCommError(this->handler, &this->errors, &this->status); // 只处理当前缓冲区已有数据,不阻塞等待 while (this->status.cbInQue > 0) { if (ReadFile(this->handler, inc_msg, 1, &bytes_read, NULL)) { if (inc_msg[0] == '<' || began) { began = true; if (inc_msg[0] == '>') { began = false; // 重置状态,准备下一帧 return complete_inc_msg; } if (inc_msg[0] != '<') { complete_inc_msg.append(inc_msg, 1); } } } else { began = false; // 出错时重置状态 return "Warning: Failed to receive data.\n"; } // 读完一个字节后再次刷新状态 ClearCommError(this->handler, &this->errors, &this->status); } // 没有完整帧时返回空字符串,避免输出空内容 return ""; }
主程序调用修改
改成非阻塞循环检查,避免程序冻结:
while (true) { std::string readResult = gyroport->readSerialPort(); if (!readResult.empty()) { std::cout << readResult << "\n"; } // 给系统留时间处理其他任务,降低CPU占用 Sleep(10); }
额外说明
static bool began用于跨函数调用保持帧解析状态,确保能连续跟踪一帧的起始和结束。- 去掉固定时长的阻塞等待,改为只处理当前缓冲区已有数据,配合主程序的短间隔循环,实现实时响应。
- 每次读取字节后刷新串口状态,保证
cbInQue是最新的缓冲区数据量。
内容的提问来源于stack exchange,提问作者sadspaceman
相关产品推荐
相关产品推荐

